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

parmoo / parmoo / 19238018279

10 Nov 2025 04:08PM UTC coverage: 83.484% (+1.3%) from 82.224%
19238018279

push

github

web-flow
Merge pull request #110 from parmoo/feature/refactor-moop

Feature/refactor moop Database

756 of 790 new or added lines in 5 files covered. (95.7%)

2 existing lines in 1 file now uncovered.

4251 of 5092 relevant lines covered (83.48%)

0.83 hits per line

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

98.84
/parmoo/databases/numpy_database.py
1

2
""" Contains the NumpyDatabase class for storing simulation results.
3

4
``parmoo.NumpyDatabase`` is the base class for storing multiobjective
5
simulation outputs. Each NumpyDatabase object may contain several
6
simulations, and their corresponding objective and constraint violation scores.
7

8
"""
9

10
import json
1✔
11
import logging
1✔
12
import numpy as np
1✔
13
from os.path import exists as file_exists
1✔
14
import pandas as pd
1✔
15

16
from parmoo.util import check_names, approx_equal
1✔
17
from parmoo.structs import SimulationDatabase
1✔
18

19

20
class NumpyDatabase(SimulationDatabase):
1✔
21
    """ A Database class specialized for multiobjective data.
22

23
    To define the NumpyDatabase, add each design variable, simulation,
24
    objective, and constraint by using the following functions:
25
     * ``NumpyDatabase.addDesign(*args)``
26
     * ``NumpyDatabase.addSimulation(*args)``
27
     * ``NumpyDatabase.addObjective(*args)``
28
     * ``NumpyDatabase.addConstraint(*args)``
29

30
    After creating a NumpyDatabase, the following methods may be useful
31
    for getting the numpy.dtype of the input/output arrays:
32
     * ``NumpyDatabase.getDesignType()``
33
     * ``NumpyDatabase.getSimulationType()``
34
     * ``NumpyDatabase.getObjectiveType()``
35
     * ``NumpyDatabase.getConstraintType()``
36

37
    To check or add to the simulation database, use:
38
     * ``NumpyDatabase.checkSimDb(x, sim_name)``
39
     * ``NumpyDatabase.updateSimDb(x, sx, sim_name)``
40

41
    To check or add to the objective database, use:
42
     * ``NumpyDatabase.checkObjDb(x)``
43
     * ``NumpyDatabase.updateObjDb(x, fx, cx)``
44

45
    Finally, the following methods are used to retrieve (filtered) simulation
46
    and objective data:
47
     * ``NumpyDatabase.isEmpty()``
48
     * ``NumpyDatabase.browseCompleteSimulations()``
49
     * ``NumpyDatabase.getPF(format='ndarray')``
50
     * ``NumpyDatabase.getSimulationData(format='ndarray')``
51
     * ``NumpyDatabase.getNewSimulationData()``
52
     * ``NumpyDatabase.getObjectiveData(format='ndarray')``
53

54
    To activate checkpointing, use:
55
     * ``NumpyDatabase.setCheckpoint(checkpoint, filename="parmoo")``
56

57
    Then to force a save or load of the current state, use:
58
     * ``NumpyDatabase.checkpointSimData(x, sx, sim_name, filename="parmoo")``
59
     * ``NumpyDatabase.checkpointObjData(x, fx, cx, filename="parmoo")``
60
     * ``NumpyDatabase.loadCheckpoint(filename="parmoo")``
61

62
    """
63

64
    __slots__ = [
1✔
65
        # Schemas
66
        'des_schema', 'sim_schema', 'obj_schema', 'con_schema',
67
        # Design tolerances for lookup
68
        'des_tols',
69
        # Compiled flag
70
        'running',
71
        # Checkpointing markers
72
        'checkpoint_data', 'checkpoint_file', 'checkpoint_new',
73
        # Database information
74
        'obj_db', 'sim_db',
75
    ]
76

77
    def __init__(self, hyperparams):
1✔
78
        """ Initializer for the NumpyDatabase class.
79

80
        Args:
81
            hyperparams (dict): Any parameters for configuring the database.
82

83
        """
84

85
        # Initialize the schemas
86
        self.des_schema, self.sim_schema = [], []
1✔
87
        self.obj_schema, self.con_schema = [], []
1✔
88
        # Initialize design tolerances for lookup
89
        self.des_tols = {}
1✔
90
        # Initialize the running flag
91
        self.running = False
1✔
92
        # Initialize checkpointing markers
93
        self.checkpoint_data = False
1✔
94
        self.checkpoint_file = "parmoo"
1✔
95
        self.checkpoint_new = True
1✔
96
        # Initialize the database
97
        self.obj_db, self.sim_db = None, None
1✔
98

99
    def addDesign(self, name, dtype, tolerance):
1✔
100
        """ Add a new design variable to the NumpyDatabase schema.
101

102
        Args:
103
            name (str): The unique name of this design variable.
104
            dtype (str): The string-representation for the numpy dtype for this
105
                design variable.
106
            tolerance (float): The tolerance up to which two different values
107
                for this design variables should be considered as "the same."
108
                If a zero is given, then only exact equality is checked.
109

110
        """
111

112
        check_names(
1✔
113
            name,
114
            self.des_schema, self.sim_schema, self.obj_schema, self.con_schema
115
        )
116
        self.des_schema.append((name, dtype))
1✔
117
        self.des_tols[name] = tolerance
1✔
118

119
    def addSimulation(self, name, m):
1✔
120
        """ Add new simulations to the NumpyDatabase schema.
121

122
        Args:
123
            name (str): The unique name of this simulation output.
124
            m (int): The number of outputs for this simulation.
125

126
        """
127

128
        check_names(
1✔
129
            name,
130
            self.des_schema, self.sim_schema, self.obj_schema, self.con_schema
131
        )
132
        if m > 1:
1✔
133
            self.sim_schema.append((name, 'f8', m))
1✔
134
        else:
135
            self.sim_schema.append((name, 'f8'))
1✔
136

137
    def addObjective(self, name):
1✔
138
        """ Add a new objective to the NumpyDatabase schema.
139

140
        Args:
141
            name (str): The unique name of this objective output.
142

143
        """
144

145
        check_names(
1✔
146
            name,
147
            self.des_schema, self.sim_schema, self.obj_schema, self.con_schema
148
        )
149
        self.obj_schema.append((name, 'f8'))
1✔
150

151
    def addConstraint(self, name):
1✔
152
        """ Add a new constraint to the NumpyDatabase schema.
153

154
        Args:
155
            name (str, optional): The unique name of this constraint violation.
156

157
        """
158

159
        check_names(
1✔
160
            name,
161
            self.des_schema, self.sim_schema, self.obj_schema, self.con_schema
162
        )
163
        self.con_schema.append((name, 'f8'))
1✔
164

165
    def getDesignType(self):
1✔
166
        """ Get the numpy dtype of all design points for this MOOP.
167

168
        Returns:
169
            dtype: The numpy dtype of this MOOP's design points.
170
            If no design variables have yet been added, returns None.
171

172
        """
173

174
        if len(self.des_schema) < 1:
1✔
175
            return None
1✔
176
        else:
177
            return np.dtype(self.des_schema)
1✔
178

179
    def getSimulationType(self):
1✔
180
        """ Get the numpy dtypes of the simulation outputs for this MOOP.
181

182
        Returns:
183
            dtype: The numpy dtype of this MOOP's simulation outputs.
184
            If no simulations have been given, returns None.
185

186
        """
187

188
        if len(self.sim_schema) < 1:
1✔
189
            return None
1✔
190
        else:
191
            return np.dtype(self.sim_schema)
1✔
192

193
    def getObjectiveType(self):
1✔
194
        """ Get the numpy dtype of an objective point for this MOOP.
195

196
        Returns:
197
            dtype: The numpy dtype of this MOOP's objective points.
198
            If no objectives have yet been added, returns None.
199

200
        """
201

202
        if len(self.obj_schema) < 1:
1✔
203
            return None
1✔
204
        else:
205
            return np.dtype(self.obj_schema)
1✔
206

207
    def getConstraintType(self):
1✔
208
        """ Get the numpy dtype of the constraint violations for this MOOP.
209

210
        Returns:
211
            dtype: The numpy dtype of this MOOP's constraint violation
212
            outputs. If no constraint functions have been given, returns None.
213

214
        """
215

216
        if len(self.con_schema) < 1:
1✔
217
            return None
1✔
218
        else:
219
            return np.dtype(self.con_schema)
1✔
220

221
    def startDatabase(self):
1✔
222
        """ Initialize the NumpyDatabase. """
223

224
        # For safety reasons, don't let silly users delete their data
225
        if not self.isEmpty():
1✔
226
            raise RuntimeError(
1✔
227
                "Cannot re-compile a MOOP with a nonempty database. "
228
                "If that's really what you want, then please reset this MOOP."
229
            )
230
        logging.info("   Initializing ParMOO's internal databases...")
1✔
231
        self.obj_db = {
1✔
232
            'x_vals': np.zeros(50, dtype=self.des_schema),
233
            'f_vals': np.zeros(50, dtype=self.obj_schema),
234
            'c_vals': np.zeros(50, dtype=self.con_schema),
235
            'n': 0,
236
        }
237
        self.sim_db = {}
1✔
238
        for stype in self.sim_schema:
1✔
239
            if len(stype) > 2:
1✔
240
                mi = stype[2]
1✔
241
            else:
242
                mi = 1
1✔
243
            self.sim_db[stype[0]] = {
1✔
244
                'x_vals': np.zeros(50, dtype=self.des_schema),
245
                's_vals': np.zeros((50, mi)),
246
                'n': 0,
247
                'n_old': 0,
248
            }
249
        self.running = True
1✔
250
        logging.info("   Done.")
1✔
251
        # If checkpointing is on, we need to start the checkpoint file
252
        if self.checkpoint_data:
1✔
253
            self._checkpoint_metadata(self.checkpoint_file)
1✔
254

255
    def checkSimDb(self, x, sim_name):
1✔
256
        """ Check self.sim_db[sim_name] to see if the design x was evaluated.
257

258
        Args:
259
            x (dict): A Python dictionary specifying the keys/names and
260
                corresponding values of a design point to search for.
261
            sim_name (str): The name of the simulation whose database will be
262
                searched.
263

264
        Returns:
265
            None or numpy.ndarray: returns None if x is not in the database
266
            for simulation "sim_name" (up to the design tolerance). Otherwise,
267
            returns the corresponding value of sx.
268

269
        """
270

271
        if not self.running:
1✔
272
            raise RuntimeError("Cannot check a database that is not running")
1✔
273
        if sim_name not in self.sim_db:
1✔
274
            raise ValueError(f"{sim_name} is not a legal name/index")
1✔
275
        for i in range(self.sim_db[sim_name]['n']):
1✔
276
            if approx_equal(
1✔
277
                x, self.sim_db[sim_name]['x_vals'][i], self.des_tols
278
            ):
279
                return self.sim_db[sim_name]['s_vals'][i]
1✔
280
        return None
1✔
281

282
    def checkObjDb(self, x):
1✔
283
        """ Check self.obj_db to see if the design x was evaluated.
284

285
        Args:
286
            x (dict): A Python dictionary specifying the keys/names and
287
                corresponding values of a design point to search for.
288

289
        Returns:
290
            None or pair of numpy.ndarrays: returns None if x is not in the
291
            database (up to the design tolerance). Otherwise, returns the
292
            corresponding value of (fx, cx) where fx is the vector-valued
293
            objective and cx is the vector-valued constraint violation at x.
294

295
        """
296

297
        if not self.running:
1✔
298
            raise RuntimeError("Cannot check a database that is not running")
1✔
299
        for i in range(self.obj_db['n']):
1✔
300
            if approx_equal(
1✔
301
                x, self.obj_db['x_vals'][i], self.des_tols
302
            ):
303
                return self.obj_db['f_vals'][i], self.obj_db['c_vals'][i]
1✔
304
        return None
1✔
305

306
    def updateSimDb(self, x, sx, sim_name):
1✔
307
        """ Update sim_db[sim_name] by adding a design/simulation output pair.
308

309
        Args:
310
            x (dict): A Python dictionary specifying the keys/names and
311
                corresponding values of a design point to add.
312

313
            sx (ndarray or list): A 1D array containing the corresponding
314
                simulation output(s).
315

316
            sim_name (str): The name of the simulation to whose database the
317
                pair (x, sx) will be added into.
318

319
        """
320

321
        if not self.running:
1✔
322
            raise RuntimeError("Cannot add to a database that is not running")
1✔
323
        if sim_name not in self.sim_db:
1✔
324
            raise ValueError(f"{sim_name} is not a legal name/index")
1✔
325
        # Convert all simulation data to flat arrays
326
        sx_flat = np.array(sx).flatten()
1✔
327
        i = self.sim_db[sim_name]['n']
1✔
328
        # Check if database needs to be resized
329
        if i >= len(self.sim_db[sim_name]['x_vals']):
1✔
330
            self.sim_db[sim_name]['x_vals'] = np.append(
1✔
331
                self.sim_db[sim_name]['x_vals'],
332
                np.zeros(i, dtype=self.des_schema), axis=0
333
            )
334
            self.sim_db[sim_name]['s_vals'] = np.append(
1✔
335
                self.sim_db[sim_name]['s_vals'],
336
                np.zeros((i, sx_flat.size)), axis=0
337
            )
338
        for key in self.des_schema:
1✔
339
            self.sim_db[sim_name]['x_vals'][key[0]][i] = x[key[0]]
1✔
340
        self.sim_db[sim_name]['s_vals'][i, :] = sx_flat[:]
1✔
341
        self.sim_db[sim_name]['n'] += 1
1✔
342
        # If various checkpointing modes are on, then save the current states
343
        if self.checkpoint_data:
1✔
344
            self.checkpointSimData(
1✔
345
                x, sx_flat, sim_name, filename=self.checkpoint_file
346
            )
347

348
    def updateObjDb(self, x, fx, cx):
1✔
349
        """ Update the internal objective database with a true evaluation of x.
350

351
        Args:
352
            x (dict): A dictionary containing the value of the design variable
353
                to add to ParMOO's database.
354
            fx (dict): A dictionary containing the values of the corresponding
355
                objective scores to add to ParMOO's database.
356
            cx (dict): A dictionary containing the values of the corresponding
357
                constraint violations to add to ParMOO's database.
358

359
        """
360

361
        if not self.running:
1✔
362
            raise RuntimeError("Cannot add to a database that is not running")
1✔
363
        # Resize the database if needed
364
        i = self.obj_db['n']
1✔
365
        if i >= len(self.obj_db['x_vals']):
1✔
366
            self.obj_db['x_vals'] = np.append(
1✔
367
                self.obj_db['x_vals'], np.zeros(i, dtype=self.des_schema),
368
                axis=0
369
            )
370
            self.obj_db['f_vals'] = np.append(
1✔
371
                self.obj_db['f_vals'], np.zeros(i, dtype=self.obj_schema),
372
                axis=0
373
            )
374
            self.obj_db['c_vals'] = np.append(
1✔
375
                self.obj_db['c_vals'], np.zeros(i, self.con_schema),
376
                axis=0
377
            )
378
        for key in self.des_schema:
1✔
379
            self.obj_db['x_vals'][key[0]][i:i+1] = x[key[0]]
1✔
380
        for key in self.obj_schema:
1✔
381
            self.obj_db['f_vals'][key[0]][i:i+1] = fx[key[0]]
1✔
382
        for key in self.con_schema:
1✔
383
            self.obj_db['c_vals'][key[0]][i:i+1] = cx[key[0]]
1✔
384
        self.obj_db['n'] += 1
1✔
385
        # If various checkpointing modes are on, then save the current states
386
        if self.checkpoint_data:
1✔
387
            self.checkpointObjData(x, fx, cx, filename=self.checkpoint_file)
1✔
388

389
    def isEmpty(self):
1✔
390
        """ Check whether the database is completely empty.
391

392
        Returns:
393
            bool: True if and only if every simulation database and the
394
            objective database is completely empty (size 0).
395

396
        """
397

398
        return (
1✔
399
            (self.obj_db is None or self.obj_db['n'] == 0) and
400
            (
401
                self.sim_db is None or
402
                all([self.sim_db[key]['n'] == 0 for key in self.sim_db])
403
            )
404
        )
405

406
    def browseCompleteSimulations(self):
1✔
407
        """ Browse all design values that are present in every sim database.
408

409
        Yields:
410
            A sequence of tuples (x, sx) where each x is a (dict) design point
411
            that is present in every internal simulation database, and sx is a
412
            dictionary of simulation outputs from each of these database.
413

414
        """
415

416
        if not self.running:
1✔
417
            raise RuntimeError("Cannot browse a database that is not running")
1✔
418
        if len(self.sim_schema) > 0:
1✔
419
            sim0 = self.sim_schema[0]
1✔
420
            n0 = self.sim_db[sim0[0]]['n']
1✔
421
            for xi, sxi in zip(
1✔
422
                self.sim_db[sim0[0]]['x_vals'][:n0],
423
                self.sim_db[sim0[0]]['s_vals'][:n0]
424
            ):
425
                # Initialize the x vals and s vals
426
                x_vals = {}
1✔
427
                for name in self.des_schema:
1✔
428
                    x_vals[name[0]] = xi[name[0]]
1✔
429
                if len(sim0) > 2:
1✔
430
                    s_vals = {sim0[0]: sxi.copy()}
1✔
431
                else:
432
                    s_vals = {sim0[0]: sxi[0]}
1✔
433
                # Look for matches in all other simulation databases
434
                matched = True
1✔
435
                for simi in self.sim_schema[1:]:
1✔
436
                    matched = False
1✔
437
                    ni = self.sim_db[simi[0]]['n']
1✔
438
                    for xj, sxj in zip(
1✔
439
                        self.sim_db[simi[0]]['x_vals'][:ni],
440
                        self.sim_db[simi[0]]['s_vals'][:ni]
441
                    ):
442
                        if approx_equal(x_vals, xj, self.des_tols):
1✔
443
                            if len(simi) > 2:
1✔
444
                                s_vals[simi[0]] = sxj.copy()
1✔
445
                            else:
NEW
446
                                s_vals[simi[0]] = sxj[0]
×
447
                            matched = True
1✔
448
                            break  # Break once we found a match
1✔
449
                    if not matched:
1✔
450
                        break  # Break if there was no match
1✔
451
                if matched:
1✔
452
                    yield x_vals, s_vals
1✔
453

454
    def getPF(self, format='ndarray'):
1✔
455
        """ Extract nondominated and efficient sets from internal databases.
456

457
        Args:
458
            format (str, optional): Either 'ndarray' (default) or 'pandas',
459
                in order to produce output as a numpy structured array or
460
                pandas dataframe. Note: format='pandas' is only valid for
461
                named inputs.
462

463
        Returns:
464
            numpy structured array or pandas DataFrame: Either a structured
465
            array or dataframe (depending on the option selected above)
466
            whose column/key names match the names of the design variables,
467
            objectives, and constraints. It contains a discrete approximation
468
            of the Pareto front and efficient set.
469

470
        """
471

472
        if not self.running:
1✔
473
            raise RuntimeError("Cannot get a database that is not running")
1✔
474
        n = self.obj_db['n']
1✔
475
        o = len(self.obj_schema)
1✔
476
        p = len(self.con_schema)
1✔
477
        dt = self.obj_schema[0][1]
1✔
478
        # Create a view of the objective and constraint values for computation
479
        f_view = self.obj_db['f_vals'][:n].view(dt).reshape(-1, o)
1✔
480
        if p > 0:
1✔
481
            c_view = self.obj_db['c_vals'][:n].view(dt).reshape(-1, p)
1✔
482
        # Initialize the output arrays
483
        ndpts = 0
1✔
484
        nondom_out = {
1✔
485
            'x_vals': np.zeros(n, dtype=self.des_schema),
486
            'f_vals': np.zeros(n, dtype=self.obj_schema),
487
            'c_vals': np.zeros(n, dtype=self.con_schema)
488
        }
489
        # Create a view of the output array for easy computations
490
        nondom_view = nondom_out['f_vals'].view(dt).reshape(n, o)
1✔
491
        # Loop over the f-values in lexicographical order
492
        lex_inds = np.lexsort(f_view.T)
1✔
493
        for i in lex_inds:
1✔
494
            if (
1✔
495
                (p == 0 or np.all(c_view[i, :] < 1e-8)) and
496
                np.all(np.any(
497
                    f_view[i, :] < nondom_view[:ndpts, :], axis=1
498
                ))
499
            ):
500
                nondom_out['x_vals'][ndpts] = self.obj_db['x_vals'][i]
1✔
501
                nondom_out['f_vals'][ndpts] = self.obj_db['f_vals'][i]
1✔
502
                nondom_out['c_vals'][ndpts] = self.obj_db['c_vals'][i]
1✔
503
                ndpts += 1
1✔
504
        # Extract the results
505
        result = np.zeros(
1✔
506
            ndpts, dtype=(self.des_schema + self.obj_schema + self.con_schema)
507
        )
508
        for dt in self.des_schema:
1✔
509
            result[dt[0]] = nondom_out['x_vals'][dt[0]][:ndpts]
1✔
510
        for dt in self.obj_schema:
1✔
511
            result[dt[0]] = nondom_out['f_vals'][dt[0]][:ndpts]
1✔
512
        for dt in self.con_schema:
1✔
513
            result[dt[0]] = nondom_out['c_vals'][dt[0]][:ndpts]
1✔
514
        if format == 'pandas':
1✔
515
            return pd.DataFrame(result)
1✔
516
        elif format == 'ndarray':
1✔
517
            return result
1✔
518
        else:
519
            raise ValueError(f"{format} is an invalid value for 'format'")
1✔
520

521
    def getSimulationData(self, format='ndarray'):
1✔
522
        """ Extract all computed simulation outputs from the MOOP's database.
523

524
        Args:
525
            format (str, optional): Either 'ndarray' (default) or 'pandas',
526
                in order to produce output as a numpy structured array or
527
                pandas dataframe. Note: format='pandas' is only valid for
528
                named inputs.
529

530
        Returns:
531
            dict: A Python dictionary whose keys match the names of the
532
            simulations. Each value is either a numpy structured array or
533
            pandas dataframe (depending on the option selected above)
534
            whose column/key names match the names of the design variables
535
            plus either and 'out' field for single-output simulations,
536
            or 'out_1', 'out_2', ... for multi-output simulations.
537

538
        """
539

540
        if not self.running:
1✔
541
            raise RuntimeError("Cannot get a database that is not running")
1✔
542
        # Build a results dict with a key for each simulation
543
        result = {}
1✔
544
        for i, sname in enumerate(self.sim_schema):
1✔
545
            # Construct the dtype for this simulation database
546
            dt = self.des_schema.copy()
1✔
547
            if len(sname) == 2:
1✔
548
                dt.append(('out', sname[1]))
1✔
549
            else:
550
                dt.append(('out', sname[1], sname[2]))
1✔
551
            # Fill the results array
552
            n = self.sim_db[sname[0]]['n']
1✔
553
            result[sname[0]] = np.zeros(n, dtype=dt)
1✔
554
            for j, xj in enumerate(self.sim_db[sname[0]]['x_vals'][:n]):
1✔
555
                for (name, t) in self.des_schema:
1✔
556
                    result[sname[0]][name][j] = xj[name]
1✔
557
            if len(sname) > 2:
1✔
558
                result[sname[0]]['out'] = self.sim_db[sname[0]]['s_vals'][:n]
1✔
559
            else:
560
                result[sname[0]]['out'] = \
1✔
561
                    self.sim_db[sname[0]]['s_vals'][:n, 0]
562
        if format == 'pandas':
1✔
563
            # For simulation data, converting to pandas is a little more
564
            # complicated...
565
            result_pd = {}
1✔
566
            for i, snamei in enumerate(result.keys()):
1✔
567
                rtempi = {}
1✔
568
                for (name, t) in self.des_schema:
1✔
569
                    rtempi[name] = result[snamei][name]
1✔
570
                # Need to break apart the output column manually
571
                if len(self.sim_schema[i]) > 2:
1✔
572
                    for j in range(self.sim_schema[i][2]):
1✔
573
                        rtempi[f'out_{j}'] = result[snamei]['out'][:, j]
1✔
574
                else:
575
                    rtempi['out'] = result[snamei]['out'][:]
1✔
576
                # Create dictionary of dataframes, indexed by sim names
577
                result_pd[snamei] = pd.DataFrame(rtempi)
1✔
578
            return result_pd
1✔
579
        elif format == 'ndarray':
1✔
580
            return result
1✔
581
        else:
582
            raise ValueError(f"{format} is an invalid value for 'format'")
1✔
583

584
    def getNewSimulationData(self):
1✔
585
        """ Extract simulation outputs that have not yet been viewed.
586

587
        Returns:
588
            dict: A Python dictionary whose keys match the names of the
589
            simulations and whose values are the new data for each
590
            variable/simulation output.
591

592
        """
593

594
        if not self.running:
1✔
595
            raise RuntimeError("Cannot get a database that is not running")
1✔
596
        # Build a results dict with a key for each simulation
597
        result = {}
1✔
598
        for sname in self.sim_schema:
1✔
599
            # Construct the dtype for this simulation database
600
            dt = self.des_schema.copy()
1✔
601
            if len(sname) > 2:
1✔
602
                dt.append(('out', sname[1], sname[2]))
1✔
603
            else:
604
                dt.append(('out', sname[1]))
1✔
605
            # Fill the results arrays with entries n_old:n
606
            result[sname[0]] = np.zeros(
1✔
607
                self.sim_db[sname[0]]['n'] - self.sim_db[sname[0]]['n_old'],
608
                dtype=dt
609
            )
610
            n_old = self.sim_db[sname[0]]['n_old']
1✔
611
            n = self.sim_db[sname[0]]['n']
1✔
612
            for j in range(n_old, n):
1✔
613
                for (name, t) in self.des_schema:
1✔
614
                    result[sname[0]][name][j - n_old] = \
1✔
615
                        self.sim_db[sname[0]]['x_vals'][name][j]
616
            if len(sname) > 2:
1✔
617
                result[sname[0]]['out'] = \
1✔
618
                    self.sim_db[sname[0]]['s_vals'][n_old:n]
619
            else:
620
                result[sname[0]]['out'] = \
1✔
621
                    self.sim_db[sname[0]]['s_vals'][n_old:n, 0]
622
            # Update the tracker
623
            self.sim_db[sname[0]]['n_old'] = n
1✔
624
        if self.checkpoint_data:
1✔
625
            self._log_new_data_call(self.checkpoint_file)
1✔
626
        return result
1✔
627

628
    def getObjectiveData(self, format='ndarray'):
1✔
629
        """ Extract all computed objective scores from this MOOP's database.
630

631
        Args:
632
            format (str, optional): Either 'ndarray' (default) or 'pandas',
633
                in order to produce output as a numpy structured array or
634
                pandas dataframe. Note: format='pandas' is only valid for
635
                named inputs.
636

637
        Returns:
638
            numpy structured array or pandas DataFrame: Either a structured
639
            array or dataframe (depending on the option selected above)
640
            whose column/key names match the names of the design variables,
641
            objectives, and constraints. It contains the results for every
642
            fully evaluated design point.
643

644
        """
645

646
        if not self.running:
1✔
647
            raise RuntimeError("Cannot get a database that is not running")
1✔
648
        # Initialize result array
649
        n = self.obj_db['n']
1✔
650
        result = np.zeros(
1✔
651
            n, dtype=(self.des_schema + self.obj_schema + self.con_schema)
652
        )
653
        # Extract all results
654
        for (name, t) in self.des_schema:
1✔
655
            result[name][:] = self.obj_db['x_vals'][name][:n]
1✔
656
        for (name, t) in self.obj_schema:
1✔
657
            result[name][:] = self.obj_db['f_vals'][name][:n]
1✔
658
        for (name, t) in self.con_schema:
1✔
659
            result[name][:] = self.obj_db['c_vals'][name][:n]
1✔
660
        if format == 'pandas':
1✔
661
            return pd.DataFrame(result)
1✔
662
        elif format == 'ndarray':
1✔
663
            return result
1✔
664
        else:
665
            raise ValueError(f"{format} is an invalid value for 'format'")
1✔
666

667
    def setCheckpoint(self, checkpoint, filename="parmoo"):
1✔
668
        """ Activate checkpointing.
669

670
        Args:
671
            checkpoint (bool): Turn checkpointing on (True) or off (False).
672
            filename (str, optional): Set the base checkpoint filename/path.
673
                The checkpoint file will have the JSON format and the
674
                extension ".simdb.json" appended to the end of filename.
675

676
        """
677

678
        if not isinstance(checkpoint, bool):
1✔
679
            raise TypeError("checkpoint must have the bool type")
1✔
680
        if not isinstance(filename, str):
1✔
681
            raise TypeError("filename must have the string type")
1✔
682
        self.checkpoint_data = checkpoint
1✔
683
        self.checkpoint_file = filename
1✔
684
        if self.running:
1✔
685
            self._checkpoint_metadata(self.checkpoint_file)
1✔
686
            # Save any pre-existing data
687
            for sim_name in self.sim_db:
1✔
688
                for i in range(self.sim_db[sim_name]['n']):
1✔
689
                    self.checkpointSimData(
1✔
690
                        self.sim_db[sim_name]['x_vals'][i],
691
                        self.sim_db[sim_name]['s_vals'][i],
692
                        sim_name,
693
                        self.checkpoint_file
694
                    )
695
            for i in range(self.obj_db['n']):
1✔
696
                self.checkpointObjData(
1✔
697
                    self.obj_db['x_vals'][i],
698
                    self.obj_db['f_vals'][i],
699
                    self.obj_db['c_vals'][i],
700
                    self.checkpoint_file
701
                )
702

703
    def checkpointSimData(self, x, sx, sim_name, filename="parmoo"):
1✔
704
        """ Append the given simulation data point to the checkpoint file.
705

706
        Args:
707
            x (dict or numpy structured element): The design value to append.
708
            sx (list or numpy array): The simulation output to append.
709
            sim_name (str): The simulation name/index to append to.
710
            filename (str, optional): The filepath to the checkpointing
711
                file(s). Do not include file extensions, they will be
712
                appended automatically. Defaults to the value "parmoo"
713
                (filename will be "parmoo.simdb.json").
714

715
        """
716

717
        # Unpack x/sx pair into a json-compatible dict for saving
718
        toadd = {
1✔
719
            'name': sim_name
720
        }
721
        for dname in self.des_schema:
1✔
722
            key = dname[0]
1✔
723
            dtype = dname[1]
1✔
724
            if dtype[0] in ["i", "u"]:
1✔
725
                toadd[key] = int(x[key])
1✔
726
            elif dtype[0] in ["f"]:
1✔
727
                toadd[key] = float(x[key])
1✔
728
            else:
729
                toadd[key] = str(x[key])
1✔
730
        toadd['out'] = [float(sxi) for sxi in sx]
1✔
731
        fname = f"{filename}.simdb.json"
1✔
732
        # Append new entries to a new line in existing file
733
        with open(fname, 'a') as fp:
1✔
734
            print(file=fp)
1✔
735
            json.dump(toadd, fp)
1✔
736

737
    def checkpointObjData(self, x, fx, cx, filename="parmoo"):
1✔
738
        """ Append the given objective data point to the checkpoint file.
739

740
        Args:
741
            x (dict or numpy structured element): The design value to append.
742
            fx (dict or numpy structured element): The objective values to
743
                append.
744
            cx (dict or numpy structured element): The constraint violations to
745
                append.
746
            filename (str, optional): The filepath to the checkpointing
747
                file(s). Do not include file extensions, they will be
748
                appended automatically. Defaults to the value "parmoo"
749
                (filename will be "parmoo.simdb.json").
750

751
        """
752

753
        # Unpack x/fx/cx into a json-compatible dict for saving
754
        toadd = {
1✔
755
            'name': "obj_db"
756
        }
757
        for dname in self.des_schema:
1✔
758
            key = dname[0]
1✔
759
            dtype = dname[1]
1✔
760
            if dtype[0] in ["i", "u"]:
1✔
761
                toadd[key] = int(x[key])
1✔
762
            elif dtype[0] in ["f"]:
1✔
763
                toadd[key] = float(x[key])
1✔
764
            else:
765
                toadd[key] = str(x[key])
1✔
766
        for oname in self.obj_schema:
1✔
767
            toadd[oname[0]] = float(fx[oname[0]])
1✔
768
        for cname in self.con_schema:
1✔
769
            toadd[cname[0]] = float(cx[cname[0]])
1✔
770
        fname = f"{filename}.simdb.json"
1✔
771
        # Append new entries to a new line in existing file
772
        with open(fname, 'a') as fp:
1✔
773
            print(file=fp)
1✔
774
            json.dump(toadd, fp)
1✔
775

776
    def loadCheckpoint(self, filename="parmoo"):
1✔
777
        """ Reload from the given checkpoint file.
778

779
        Args:
780
            filename (str, optional): The filepath to the checkpointing
781
                file(s). Do not include file extensions, they will be
782
                appended automatically. Defaults to the value "parmoo"
783
                (filename will be "parmoo.simdb.json").
784

785
        """
786

787
        if not self.isEmpty():
1✔
NEW
788
            raise RuntimeError(
×
789
                "Attempting to load a previous checkpoint but the database"
790
                " is non empty. Proceeding could overwrite existing"
791
                " data or create incosistent states. Please save any existing"
792
                " data and reset the database before proceeding."
793
            )
794
        with open(f"{filename}.simdb.json", 'r') as fp:
1✔
795
            for i, linei in enumerate(fp):
1✔
796
                entryi = json.loads(linei)
1✔
797
                if 'name' not in entryi:
1✔
NEW
798
                    raise IOError(
×
799
                        f"{filename}.simdb.json contains an invalid entry."
800
                    )
801
                elif i == 0:
1✔
802
                    if entryi['name'] != 'metadata':
1✔
NEW
803
                        raise IOError(
×
804
                            f"{filename}.simdb.json is missing the metadata"
805
                            " header."
806
                        )
807
                    self.des_schema = [
1✔
808
                        tuple(tj) for tj in entryi['des_schema']
809
                    ]
810
                    self.sim_schema = [
1✔
811
                        tuple(tj) for tj in entryi['sim_schema']
812
                    ]
813
                    self.obj_schema = [
1✔
814
                        tuple(tj) for tj in entryi['obj_schema']
815
                    ]
816
                    self.con_schema = [
1✔
817
                        tuple(tj) for tj in entryi['con_schema']
818
                    ]
819
                    self.des_tols = entryi['des_tols']
1✔
820
                    self.checkpoint_data = False  # Disable temporarily
1✔
821
                    self.checkpoint_file = filename
1✔
822
                    self.checkpoint_new = False
1✔
823
                    self.startDatabase()
1✔
824
                elif entryi['name'] == 'obj_db':
1✔
825
                    x = {}
1✔
826
                    for key in self.des_schema:
1✔
827
                        x[key[0]] = entryi[key[0]]
1✔
828
                    fx = {}
1✔
829
                    for key in self.obj_schema:
1✔
830
                        fx[key[0]] = entryi[key[0]]
1✔
831
                    cx = {}
1✔
832
                    for key in self.con_schema:
1✔
833
                        cx[key[0]] = entryi[key[0]]
1✔
834
                    self.updateObjDb(x, fx, cx)
1✔
835
                elif entryi['name'] == "get_new_data":
1✔
836
                    for key in self.sim_db:
1✔
837
                        self.sim_db[key]['n_old'] = self.sim_db[key]['n']
1✔
838
                else:
839
                    x = {}
1✔
840
                    for key in self.des_schema:
1✔
841
                        x[key[0]] = entryi[key[0]]
1✔
842
                    sx = entryi['out']
1✔
843
                    sim_name = entryi['name']
1✔
844
                    self.updateSimDb(x, sx, sim_name)
1✔
845
        self.checkpoint_data = True  # Re-enable
1✔
846

847
    def _checkpoint_metadata(self, filename="parmoo"):
1✔
848
        """ Private helper to write metadata to the checkpoint file. """
849

850
        fname = f"{filename}.simdb.json"
1✔
851
        # Don't overwrite existing data when the new data flag is set
852
        if self.checkpoint_new and file_exists(fname):
1✔
853
            raise OSError(
1✔
854
                f"Creating a new save file, but {filename}.simdb.json already"
855
                " exists! Move the existing file to a new location, delete it"
856
                " or load it first so that ParMOO doesn't overwrite your"
857
                " existing data..."
858
            )
859
        with open(fname, "w") as fp:
1✔
860
            json.dump({
1✔
861
                'name': "metadata",
862
                'des_schema': self.des_schema,
863
                'sim_schema': self.sim_schema,
864
                'obj_schema': self.obj_schema,
865
                'con_schema': self.con_schema,
866
                'des_tols': self.des_tols,
867
            }, fp)
868
        self.checkpoint_new = False
1✔
869

870
    def _log_new_data_call(self, filename="parmoo"):
1✔
871
        """ Private helper to log when new data was requested. """
872

873
        fname = f"{filename}.simdb.json"
1✔
874
        with open(fname, "a") as fp:
1✔
875
            print(file=fp)
1✔
876
            json.dump({'name': "get_new_data"}, fp)
1✔
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