easyvvuq.data_structs

Data structures to ensure consistency during serialization for databases.

  1"""Data structures to ensure consistency during serialization for databases.
  2
  3"""
  4import os
  5import logging
  6import json
  7from easyvvuq import constants
  8from easyvvuq.utils.helpers import easyvvuq_serialize
  9import numpy
 10
 11__copyright__ = """
 12
 13    Copyright 2018 Robin A. Richardson, David W. Wright
 14
 15    This file is part of EasyVVUQ
 16
 17    EasyVVUQ is free software: you can redistribute it and/or modify
 18    it under the terms of the Lesser GNU General Public License as published by
 19    the Free Software Foundation, either version 3 of the License, or
 20    (at your option) any later version.
 21
 22    EasyVVUQ is distributed in the hope that it will be useful,
 23    but WITHOUT ANY WARRANTY; without even the implied warranty of
 24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25    Lesser GNU General Public License for more details.
 26
 27    You should have received a copy of the Lesser GNU General Public License
 28    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 29
 30"""
 31__license__ = "LGPL"
 32
 33logger = logging.getLogger(__name__)
 34
 35
 36def check_local_dir(path, dir_type='campaign'):
 37    """
 38    Check that local path exists and if not create it.
 39
 40    Parameters
 41    ----------
 42    path : str
 43        Directory location to check.
 44    dir_type : str, default='campaign'
 45        Type of directory we are checking (used for user and debugging
 46        information.)
 47
 48    Returns
 49    -------
 50
 51    """
 52
 53    if not os.path.isdir(path):
 54
 55        if os.path.exists(path):
 56            logger.critical(f'{path} specified as {dir_type} directory '
 57                            f'for local run but is not a directory.')
 58            raise IOError(f'Invalid {dir_type} directory')
 59        else:
 60            os.makedirs(path)
 61
 62
 63def check_reference(ref, run_name, ref_type='campaign'):
 64    """
 65    Validation check for a `RunInfo` reference. Checks that an integer value
 66    has been passed to use as a reference to another 'table' - i.e. to a
 67    specific campaign, app or sampler.
 68
 69    Parameters
 70    ----------
 71    ref : int
 72        Reference to be checked.
 73    run_name : str
 74        Name of run for which the check is being performed (user info/
 75        debugging).
 76    ref_type : str, default='campaign'
 77        Are we checking for a campaign, sampler or app (user info/
 78        debugging).
 79
 80    Returns
 81    -------
 82
 83    """
 84
 85    if ref is None:
 86        message = f'No {ref_type} id specified for run {run_name}'
 87        logger.critical(message)
 88        raise RuntimeError(message)
 89
 90    if not isinstance(ref, int):
 91        message = (f'Invalid {ref_type} id ({ref}) specified for '
 92                   f'run {run_name}')
 93        logger.critical(message)
 94        raise RuntimeError(message)
 95
 96
 97class RunInfo:
 98    """Handles information for individual application runs.
 99
100    Parameters
101    ----------
102    run_name : str
103        Human readable name of the run.
104    app : None or int
105        ID of the associated application.
106    params : None or dict
107        Dictionary of parameter values for this run.
108    sample: None or int
109        ID of the sampler that created the run.
110    campaign: None or int
111        ID of the associated campaign.
112
113    Attributes
114    ----------
115    campaign : int
116        ID of the associated campaign.
117    sample : int
118        ID of the sampler that created the run.
119    app : int
120        ID of the associated application.
121    run_name : str
122        Human readable name of the run.
123    status : enum(Status)
124    """
125
126    def __init__(
127            self,
128            run_name=None,
129            run_dir=None,
130            app=None,
131            params=None,
132            sample=None,
133            campaign=None,
134            status=constants.Status.NEW):
135
136        self.campaign = campaign
137        self.sample = sample
138        self.app = app
139        self.run_name = run_name
140        self.run_dir = run_dir
141
142        if not params:
143            message = f'No run configuration specified for run {run_name}'
144            raise RuntimeError(message)
145
146        self.params = params
147        self.status = status
148
149        self.iteration = 0
150
151    def to_dict(self, flatten=False):
152        """Convert to a dictionary (optionally flatten to single level)
153
154        Parameters
155        ----------
156        flatten : bool
157            Should the return dictionary be single level (i.e. should `params`
158            or other dictionary variables be serialized).
159
160        Returns
161        -------
162        dict
163            Dictionary representing the run - if flattened then params are
164            returned as a JSON format sting.
165        """
166
167        def convert_nonserializable(obj):
168            if isinstance(obj, numpy.int64):
169                return int(obj)
170            raise TypeError('Unknown type:', type(obj))
171
172        if flatten:
173
174            out_dict = {
175                'run_name': self.run_name,
176                'run_dir': self.run_dir,
177                'params': json.dumps(self.params, default=convert_nonserializable),
178                'status': constants.Status(self.status),
179                'campaign': self.campaign,
180                'sampler': self.sample,
181                'app': self.app,
182                'iteration': self.iteration,
183            }
184
185        else:
186
187            out_dict = {
188                'run_name': self.run_name,
189                'run_dir': self.run_dir,
190                'params': self.params,
191                'status': constants.Status(self.status),
192                'campaign': self.campaign,
193                'sampler': self.sample,
194                'app': self.app,
195                'iteration': self.iteration,
196            }
197
198        return out_dict
199
200
201class AppInfo:
202    """Handles information for particular application.
203
204    Attributes
205    ----------
206    name : str or None
207        Human readable application name.
208    paramsspec : ParamsSpecification or None
209        Description of possible parameter values.
210    """
211
212    def __init__(
213            self,
214            name=None,
215            paramsspec=None,
216            actions=None):
217
218        self.name = name
219        self.paramsspec = paramsspec
220        self.actions = actions
221
222    def to_dict(self, flatten=False):
223        """Convert to a dictionary (optionally flatten to single level)
224
225        Parameters
226        ----------
227        flatten : bool
228            Should the return dictionary be single level (i.e. should `paramsspec`
229            be serialized).
230
231        Returns
232        -------
233        dict
234            Dictionary representing the application- if flattened then `paramsspec`
235            is returned as a JSON format sting.
236        """
237
238        if flatten:
239
240            out_dict = self.to_dict()
241
242            out_dict['params'] = self.paramsspec.serialize()
243        else:
244
245            out_dict = {
246                'name': self.name,
247                'params': self.paramsspec,
248                'actions': easyvvuq_serialize(self.actions),
249            }
250
251        return out_dict
252
253
254class CampaignInfo:
255    """Handles information on Campaign.
256
257    Parameters
258    ----------
259    name : str or None
260        Human readable campaign name.
261    easyvvuq_version : str or None
262        Version of EasyVVUQ used to create the campaign.
263    campaign_dir_prefix : str or None
264        Prefix test for campaign directory.
265    campaign_dir : str or None,
266        Path to the campaign directory.
267    runs_dir : str or None
268        path to run directory (within the campaign directory)
269    local : bool, default=False
270        Is this campaign designed to be created and executed on the same
271        machine?
272
273    Attributes
274    ----------
275    name : str or None
276        Human readable campaign name.
277    easyvvuq_version : str or None
278        Version of EasyVVUQ used to create the campaign.
279    campaign_dir_prefix : str or None
280        Prefix test for campaign directory.
281    campaign_dir : str or None,
282        Path to the campaign directory.
283    runs_dir : str or None
284        path to run directory (within the campaign directory)
285    """
286
287    def __init__(self, name=None, easyvvuq_version=None,
288                 campaign_dir_prefix=None, campaign_dir=None,
289                 runs_dir=None, local=False):
290
291        if name is None:
292            message = "CampaignInfo constructor must be passed a 'name'."
293            logger.critical(message)
294            raise RuntimeError(message)
295
296        if campaign_dir is None:
297            message = "CampaignInfo constructor must be passed 'campaign_dir'"
298            logger.critical(message)
299            raise RuntimeError(message)
300
301        self.name = name
302        self.campaign_dir_prefix = campaign_dir_prefix
303
304        self.easyvvuq_version = easyvvuq_version
305
306        # TODO: think about right location for path check for remote runs
307        if local:
308            check_local_dir(campaign_dir)
309
310        self.campaign_dir = campaign_dir
311
312        if runs_dir is None:
313            runs_dir = os.path.join(campaign_dir, 'runs')
314
315        if local:
316            check_local_dir(runs_dir, 'runs')
317
318        self.runs_dir = runs_dir
319
320    @property
321    def easyvvuq_version(self):
322        return self._easyvvuq_version
323
324    @easyvvuq_version.setter
325    def easyvvuq_version(self, version_no):
326        # TODO: check validity and compatibility
327        self._easyvvuq_version = version_no
328
329    def to_dict(self, flatten=False):
330        """Convert this to a dictionary
331
332        Parameters
333        ----------
334        flatten : bool
335            Should the return dictionary be single level (always true here).
336
337        Returns
338        -------
339        dict
340            Dictionary representing the campaign.
341        """
342
343        out_dict = {
344            'name': self.name,
345            'campaign_dir': self.campaign_dir,
346            'campaign_dir_prefix': self.campaign_dir_prefix,
347            'runs_dir': self.runs_dir,
348            'easyvvuq_version': self.easyvvuq_version
349        }
350
351        return out_dict
logger = <Logger easyvvuq.data_structs (DEBUG)>
def check_local_dir(path, dir_type='campaign'):
37def check_local_dir(path, dir_type='campaign'):
38    """
39    Check that local path exists and if not create it.
40
41    Parameters
42    ----------
43    path : str
44        Directory location to check.
45    dir_type : str, default='campaign'
46        Type of directory we are checking (used for user and debugging
47        information.)
48
49    Returns
50    -------
51
52    """
53
54    if not os.path.isdir(path):
55
56        if os.path.exists(path):
57            logger.critical(f'{path} specified as {dir_type} directory '
58                            f'for local run but is not a directory.')
59            raise IOError(f'Invalid {dir_type} directory')
60        else:
61            os.makedirs(path)

Check that local path exists and if not create it.

Parameters
  • path (str): Directory location to check.
  • dir_type (str, default='campaign'): Type of directory we are checking (used for user and debugging information.)
  • Returns
  • -------
def check_reference(ref, run_name, ref_type='campaign'):
64def check_reference(ref, run_name, ref_type='campaign'):
65    """
66    Validation check for a `RunInfo` reference. Checks that an integer value
67    has been passed to use as a reference to another 'table' - i.e. to a
68    specific campaign, app or sampler.
69
70    Parameters
71    ----------
72    ref : int
73        Reference to be checked.
74    run_name : str
75        Name of run for which the check is being performed (user info/
76        debugging).
77    ref_type : str, default='campaign'
78        Are we checking for a campaign, sampler or app (user info/
79        debugging).
80
81    Returns
82    -------
83
84    """
85
86    if ref is None:
87        message = f'No {ref_type} id specified for run {run_name}'
88        logger.critical(message)
89        raise RuntimeError(message)
90
91    if not isinstance(ref, int):
92        message = (f'Invalid {ref_type} id ({ref}) specified for '
93                   f'run {run_name}')
94        logger.critical(message)
95        raise RuntimeError(message)

Validation check for a RunInfo reference. Checks that an integer value has been passed to use as a reference to another 'table' - i.e. to a specific campaign, app or sampler.

Parameters
  • ref (int): Reference to be checked.
  • run_name (str): Name of run for which the check is being performed (user info/ debugging).
  • ref_type (str, default='campaign'): Are we checking for a campaign, sampler or app (user info/ debugging).
  • Returns
  • -------
class RunInfo:
 98class RunInfo:
 99    """Handles information for individual application runs.
100
101    Parameters
102    ----------
103    run_name : str
104        Human readable name of the run.
105    app : None or int
106        ID of the associated application.
107    params : None or dict
108        Dictionary of parameter values for this run.
109    sample: None or int
110        ID of the sampler that created the run.
111    campaign: None or int
112        ID of the associated campaign.
113
114    Attributes
115    ----------
116    campaign : int
117        ID of the associated campaign.
118    sample : int
119        ID of the sampler that created the run.
120    app : int
121        ID of the associated application.
122    run_name : str
123        Human readable name of the run.
124    status : enum(Status)
125    """
126
127    def __init__(
128            self,
129            run_name=None,
130            run_dir=None,
131            app=None,
132            params=None,
133            sample=None,
134            campaign=None,
135            status=constants.Status.NEW):
136
137        self.campaign = campaign
138        self.sample = sample
139        self.app = app
140        self.run_name = run_name
141        self.run_dir = run_dir
142
143        if not params:
144            message = f'No run configuration specified for run {run_name}'
145            raise RuntimeError(message)
146
147        self.params = params
148        self.status = status
149
150        self.iteration = 0
151
152    def to_dict(self, flatten=False):
153        """Convert to a dictionary (optionally flatten to single level)
154
155        Parameters
156        ----------
157        flatten : bool
158            Should the return dictionary be single level (i.e. should `params`
159            or other dictionary variables be serialized).
160
161        Returns
162        -------
163        dict
164            Dictionary representing the run - if flattened then params are
165            returned as a JSON format sting.
166        """
167
168        def convert_nonserializable(obj):
169            if isinstance(obj, numpy.int64):
170                return int(obj)
171            raise TypeError('Unknown type:', type(obj))
172
173        if flatten:
174
175            out_dict = {
176                'run_name': self.run_name,
177                'run_dir': self.run_dir,
178                'params': json.dumps(self.params, default=convert_nonserializable),
179                'status': constants.Status(self.status),
180                'campaign': self.campaign,
181                'sampler': self.sample,
182                'app': self.app,
183                'iteration': self.iteration,
184            }
185
186        else:
187
188            out_dict = {
189                'run_name': self.run_name,
190                'run_dir': self.run_dir,
191                'params': self.params,
192                'status': constants.Status(self.status),
193                'campaign': self.campaign,
194                'sampler': self.sample,
195                'app': self.app,
196                'iteration': self.iteration,
197            }
198
199        return out_dict

Handles information for individual application runs.

Parameters
  • run_name (str): Human readable name of the run.
  • app (None or int): ID of the associated application.
  • params (None or dict): Dictionary of parameter values for this run.
  • sample (None or int): ID of the sampler that created the run.
  • campaign (None or int): ID of the associated campaign.
Attributes
  • campaign (int): ID of the associated campaign.
  • sample (int): ID of the sampler that created the run.
  • app (int): ID of the associated application.
  • run_name (str): Human readable name of the run.
  • status (enum(Status)):
RunInfo( run_name=None, run_dir=None, app=None, params=None, sample=None, campaign=None, status=<Status.NEW: 1>)
127    def __init__(
128            self,
129            run_name=None,
130            run_dir=None,
131            app=None,
132            params=None,
133            sample=None,
134            campaign=None,
135            status=constants.Status.NEW):
136
137        self.campaign = campaign
138        self.sample = sample
139        self.app = app
140        self.run_name = run_name
141        self.run_dir = run_dir
142
143        if not params:
144            message = f'No run configuration specified for run {run_name}'
145            raise RuntimeError(message)
146
147        self.params = params
148        self.status = status
149
150        self.iteration = 0
campaign
sample
app
run_name
run_dir
params
status
iteration
def to_dict(self, flatten=False):
152    def to_dict(self, flatten=False):
153        """Convert to a dictionary (optionally flatten to single level)
154
155        Parameters
156        ----------
157        flatten : bool
158            Should the return dictionary be single level (i.e. should `params`
159            or other dictionary variables be serialized).
160
161        Returns
162        -------
163        dict
164            Dictionary representing the run - if flattened then params are
165            returned as a JSON format sting.
166        """
167
168        def convert_nonserializable(obj):
169            if isinstance(obj, numpy.int64):
170                return int(obj)
171            raise TypeError('Unknown type:', type(obj))
172
173        if flatten:
174
175            out_dict = {
176                'run_name': self.run_name,
177                'run_dir': self.run_dir,
178                'params': json.dumps(self.params, default=convert_nonserializable),
179                'status': constants.Status(self.status),
180                'campaign': self.campaign,
181                'sampler': self.sample,
182                'app': self.app,
183                'iteration': self.iteration,
184            }
185
186        else:
187
188            out_dict = {
189                'run_name': self.run_name,
190                'run_dir': self.run_dir,
191                'params': self.params,
192                'status': constants.Status(self.status),
193                'campaign': self.campaign,
194                'sampler': self.sample,
195                'app': self.app,
196                'iteration': self.iteration,
197            }
198
199        return out_dict

Convert to a dictionary (optionally flatten to single level)

Parameters
  • flatten (bool): Should the return dictionary be single level (i.e. should params or other dictionary variables be serialized).
Returns
  • dict: Dictionary representing the run - if flattened then params are returned as a JSON format sting.
class AppInfo:
202class AppInfo:
203    """Handles information for particular application.
204
205    Attributes
206    ----------
207    name : str or None
208        Human readable application name.
209    paramsspec : ParamsSpecification or None
210        Description of possible parameter values.
211    """
212
213    def __init__(
214            self,
215            name=None,
216            paramsspec=None,
217            actions=None):
218
219        self.name = name
220        self.paramsspec = paramsspec
221        self.actions = actions
222
223    def to_dict(self, flatten=False):
224        """Convert to a dictionary (optionally flatten to single level)
225
226        Parameters
227        ----------
228        flatten : bool
229            Should the return dictionary be single level (i.e. should `paramsspec`
230            be serialized).
231
232        Returns
233        -------
234        dict
235            Dictionary representing the application- if flattened then `paramsspec`
236            is returned as a JSON format sting.
237        """
238
239        if flatten:
240
241            out_dict = self.to_dict()
242
243            out_dict['params'] = self.paramsspec.serialize()
244        else:
245
246            out_dict = {
247                'name': self.name,
248                'params': self.paramsspec,
249                'actions': easyvvuq_serialize(self.actions),
250            }
251
252        return out_dict

Handles information for particular application.

Attributes
  • name (str or None): Human readable application name.
  • paramsspec (ParamsSpecification or None): Description of possible parameter values.
AppInfo(name=None, paramsspec=None, actions=None)
213    def __init__(
214            self,
215            name=None,
216            paramsspec=None,
217            actions=None):
218
219        self.name = name
220        self.paramsspec = paramsspec
221        self.actions = actions
name
paramsspec
actions
def to_dict(self, flatten=False):
223    def to_dict(self, flatten=False):
224        """Convert to a dictionary (optionally flatten to single level)
225
226        Parameters
227        ----------
228        flatten : bool
229            Should the return dictionary be single level (i.e. should `paramsspec`
230            be serialized).
231
232        Returns
233        -------
234        dict
235            Dictionary representing the application- if flattened then `paramsspec`
236            is returned as a JSON format sting.
237        """
238
239        if flatten:
240
241            out_dict = self.to_dict()
242
243            out_dict['params'] = self.paramsspec.serialize()
244        else:
245
246            out_dict = {
247                'name': self.name,
248                'params': self.paramsspec,
249                'actions': easyvvuq_serialize(self.actions),
250            }
251
252        return out_dict

Convert to a dictionary (optionally flatten to single level)

Parameters
  • flatten (bool): Should the return dictionary be single level (i.e. should paramsspec be serialized).
Returns
  • dict: Dictionary representing the application- if flattened then paramsspec is returned as a JSON format sting.
class CampaignInfo:
255class CampaignInfo:
256    """Handles information on Campaign.
257
258    Parameters
259    ----------
260    name : str or None
261        Human readable campaign name.
262    easyvvuq_version : str or None
263        Version of EasyVVUQ used to create the campaign.
264    campaign_dir_prefix : str or None
265        Prefix test for campaign directory.
266    campaign_dir : str or None,
267        Path to the campaign directory.
268    runs_dir : str or None
269        path to run directory (within the campaign directory)
270    local : bool, default=False
271        Is this campaign designed to be created and executed on the same
272        machine?
273
274    Attributes
275    ----------
276    name : str or None
277        Human readable campaign name.
278    easyvvuq_version : str or None
279        Version of EasyVVUQ used to create the campaign.
280    campaign_dir_prefix : str or None
281        Prefix test for campaign directory.
282    campaign_dir : str or None,
283        Path to the campaign directory.
284    runs_dir : str or None
285        path to run directory (within the campaign directory)
286    """
287
288    def __init__(self, name=None, easyvvuq_version=None,
289                 campaign_dir_prefix=None, campaign_dir=None,
290                 runs_dir=None, local=False):
291
292        if name is None:
293            message = "CampaignInfo constructor must be passed a 'name'."
294            logger.critical(message)
295            raise RuntimeError(message)
296
297        if campaign_dir is None:
298            message = "CampaignInfo constructor must be passed 'campaign_dir'"
299            logger.critical(message)
300            raise RuntimeError(message)
301
302        self.name = name
303        self.campaign_dir_prefix = campaign_dir_prefix
304
305        self.easyvvuq_version = easyvvuq_version
306
307        # TODO: think about right location for path check for remote runs
308        if local:
309            check_local_dir(campaign_dir)
310
311        self.campaign_dir = campaign_dir
312
313        if runs_dir is None:
314            runs_dir = os.path.join(campaign_dir, 'runs')
315
316        if local:
317            check_local_dir(runs_dir, 'runs')
318
319        self.runs_dir = runs_dir
320
321    @property
322    def easyvvuq_version(self):
323        return self._easyvvuq_version
324
325    @easyvvuq_version.setter
326    def easyvvuq_version(self, version_no):
327        # TODO: check validity and compatibility
328        self._easyvvuq_version = version_no
329
330    def to_dict(self, flatten=False):
331        """Convert this to a dictionary
332
333        Parameters
334        ----------
335        flatten : bool
336            Should the return dictionary be single level (always true here).
337
338        Returns
339        -------
340        dict
341            Dictionary representing the campaign.
342        """
343
344        out_dict = {
345            'name': self.name,
346            'campaign_dir': self.campaign_dir,
347            'campaign_dir_prefix': self.campaign_dir_prefix,
348            'runs_dir': self.runs_dir,
349            'easyvvuq_version': self.easyvvuq_version
350        }
351
352        return out_dict

Handles information on Campaign.

Parameters
  • name (str or None): Human readable campaign name.
  • easyvvuq_version (str or None): Version of EasyVVUQ used to create the campaign.
  • campaign_dir_prefix (str or None): Prefix test for campaign directory.
  • campaign_dir (str or None,): Path to the campaign directory.
  • runs_dir (str or None): path to run directory (within the campaign directory)
  • local (bool, default=False): Is this campaign designed to be created and executed on the same machine?
Attributes
  • name (str or None): Human readable campaign name.
  • easyvvuq_version (str or None): Version of EasyVVUQ used to create the campaign.
  • campaign_dir_prefix (str or None): Prefix test for campaign directory.
  • campaign_dir (str or None,): Path to the campaign directory.
  • runs_dir (str or None): path to run directory (within the campaign directory)
CampaignInfo( name=None, easyvvuq_version=None, campaign_dir_prefix=None, campaign_dir=None, runs_dir=None, local=False)
288    def __init__(self, name=None, easyvvuq_version=None,
289                 campaign_dir_prefix=None, campaign_dir=None,
290                 runs_dir=None, local=False):
291
292        if name is None:
293            message = "CampaignInfo constructor must be passed a 'name'."
294            logger.critical(message)
295            raise RuntimeError(message)
296
297        if campaign_dir is None:
298            message = "CampaignInfo constructor must be passed 'campaign_dir'"
299            logger.critical(message)
300            raise RuntimeError(message)
301
302        self.name = name
303        self.campaign_dir_prefix = campaign_dir_prefix
304
305        self.easyvvuq_version = easyvvuq_version
306
307        # TODO: think about right location for path check for remote runs
308        if local:
309            check_local_dir(campaign_dir)
310
311        self.campaign_dir = campaign_dir
312
313        if runs_dir is None:
314            runs_dir = os.path.join(campaign_dir, 'runs')
315
316        if local:
317            check_local_dir(runs_dir, 'runs')
318
319        self.runs_dir = runs_dir
name
campaign_dir_prefix
easyvvuq_version
321    @property
322    def easyvvuq_version(self):
323        return self._easyvvuq_version
campaign_dir
runs_dir
def to_dict(self, flatten=False):
330    def to_dict(self, flatten=False):
331        """Convert this to a dictionary
332
333        Parameters
334        ----------
335        flatten : bool
336            Should the return dictionary be single level (always true here).
337
338        Returns
339        -------
340        dict
341            Dictionary representing the campaign.
342        """
343
344        out_dict = {
345            'name': self.name,
346            'campaign_dir': self.campaign_dir,
347            'campaign_dir_prefix': self.campaign_dir_prefix,
348            'runs_dir': self.runs_dir,
349            'easyvvuq_version': self.easyvvuq_version
350        }
351
352        return out_dict

Convert this to a dictionary

Parameters
  • flatten (bool): Should the return dictionary be single level (always true here).
Returns
  • dict: Dictionary representing the campaign.