Skip to content

base

PROCESS MFILE.DAT IO library

MFileVariable

Bases: dict

Class for containing a single mfile variable

Source code in process/core/io/mfile/base.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class MFileVariable(dict):  # noqa: FURB189
    """Class for containing a single mfile variable"""

    def __init__(
        self, var_name, var_description, var_unit=None, var_flag=None, *args, **kwargs
    ):
        """
        An object class to contain information (and data values) for a single
        variable from the PROCESS machine readable output file (MFILE.DAT).
        Each class can contain all the scan values for the variable.

        Init defines the variable name and description from arguments:
          var_name --> variable name
          var_description --> variable description/long name

        The class contains the following:
            set_scan    --> function to set a scan number to a given value
            get_scan    --> function to retrieve a given scan
            get_scans   --> function to retrieve all scans.
        """
        self.var_name = var_name
        self.var_description = var_description
        self.var_unit = var_unit
        self.var_flag = var_flag
        self.latest_scan = 0
        super().__init__(*args, **kwargs)

    def __getattr__(self, name):
        result = self.get(name)
        if result:
            return result
        raise AttributeError(f"{self.__class__} object has no attribute {name}")

    def set_scan(self, scan_number, scan_value):
        """Sets the class attribute self.scan# where # is scan number

        Parameters
        ----------
        scan_number :
            scan number
        scan_value :
            value of parameter for scan
        """
        self[f"scan{scan_number:02}"] = scan_value
        self.latest_scan = max(self.latest_scan, scan_number)

    def get_scan(self, scan_number):
        """Returns the value of a specific scan. For scan = -1 or None the last
        scan is given.

        Parameters
        ----------
        scan_number :
            scan number to return

        Returns
        -------
        type
            [single scan requested]
        """
        if scan_number is None or scan_number == -1:
            return self[f"scan{self.latest_scan:02}"]
        return self[f"scan{scan_number:02}"]

    def get_scans(self):
        """Returns a list of scan values in order of scan number

        Returns
        -------
            [List of all scans for variable]
        """
        return [v for k, v in sorted(filter(lambda x: "scan" in x[0], self.items()))]

    def get_number_of_scans(self):
        """Function to return the number of scans in the variable class"""
        return len([key for key in self.keys() if "scan" in key])

    @property
    def exists(self):
        return True

var_name = var_name instance-attribute

var_description = var_description instance-attribute

var_unit = var_unit instance-attribute

var_flag = var_flag instance-attribute

latest_scan = 0 instance-attribute

exists property

set_scan(scan_number, scan_value)

Sets the class attribute self.scan# where # is scan number

Parameters:

Name Type Description Default
scan_number

scan number

required
scan_value

value of parameter for scan

required
Source code in process/core/io/mfile/base.py
55
56
57
58
59
60
61
62
63
64
65
66
def set_scan(self, scan_number, scan_value):
    """Sets the class attribute self.scan# where # is scan number

    Parameters
    ----------
    scan_number :
        scan number
    scan_value :
        value of parameter for scan
    """
    self[f"scan{scan_number:02}"] = scan_value
    self.latest_scan = max(self.latest_scan, scan_number)

get_scan(scan_number)

Returns the value of a specific scan. For scan = -1 or None the last scan is given.

Parameters:

Name Type Description Default
scan_number

scan number to return

required

Returns:

Type Description
type

[single scan requested]

Source code in process/core/io/mfile/base.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def get_scan(self, scan_number):
    """Returns the value of a specific scan. For scan = -1 or None the last
    scan is given.

    Parameters
    ----------
    scan_number :
        scan number to return

    Returns
    -------
    type
        [single scan requested]
    """
    if scan_number is None or scan_number == -1:
        return self[f"scan{self.latest_scan:02}"]
    return self[f"scan{scan_number:02}"]

get_scans()

Returns a list of scan values in order of scan number

Returns:

Type Description
[List of all scans for variable]
Source code in process/core/io/mfile/base.py
86
87
88
89
90
91
92
93
def get_scans(self):
    """Returns a list of scan values in order of scan number

    Returns
    -------
        [List of all scans for variable]
    """
    return [v for k, v in sorted(filter(lambda x: "scan" in x[0], self.items()))]

get_number_of_scans()

Function to return the number of scans in the variable class

Source code in process/core/io/mfile/base.py
95
96
97
def get_number_of_scans(self):
    """Function to return the number of scans in the variable class"""
    return len([key for key in self.keys() if "scan" in key])

MFileErrorClass

Error class for handling missing data from MFILE

Source code in process/core/io/mfile/base.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class MFileErrorClass:
    """Error class for handling missing data from MFILE"""

    def __init__(self, item):
        self.item = item
        self.get_scan = self.get_error
        self.get_scans = self.get_error
        self.set_scan = self.get_error
        self.get_number_of_scans = self.get_error

    def get_error(self, *args, **kwargs):  # noqa: ARG002
        logger.error(f"Key '{self.item}' not in MFILE. KeyError! Check MFILE")

        if self.item == "error_status":
            # Missing error_status key means Process exited prematurely, usually
            # due to a "STOP 1"
            raise KeyError(
                "error_status not found in MFILE. Process probably exited prematurely"
            )
        return 0

    @property
    def exists(self):
        return False

item = item instance-attribute

get_scan = self.get_error instance-attribute

get_scans = self.get_error instance-attribute

set_scan = self.get_error instance-attribute

get_number_of_scans = self.get_error instance-attribute

exists property

get_error(*args, **kwargs)

Source code in process/core/io/mfile/base.py
114
115
116
117
118
119
120
121
122
123
def get_error(self, *args, **kwargs):  # noqa: ARG002
    logger.error(f"Key '{self.item}' not in MFILE. KeyError! Check MFILE")

    if self.item == "error_status":
        # Missing error_status key means Process exited prematurely, usually
        # due to a "STOP 1"
        raise KeyError(
            "error_status not found in MFILE. Process probably exited prematurely"
        )
    return 0

MFileDataDictionary

Bases: OrderedDict

Class object to act as a dictionary for the data.

Source code in process/core/io/mfile/base.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class MFileDataDictionary(OrderedDict):
    """Class object to act as a dictionary for the data."""

    def __getattr__(self, name):
        result = self.get(name)
        if result:
            return result
        raise AttributeError(f"{self.__class__} object has no attribute {name}")

    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            return MFileErrorClass(item)

DefaultOrderedDict

Bases: OrderedDict

Source code in process/core/io/mfile/base.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
class DefaultOrderedDict(OrderedDict):
    # Source: http://stackoverflow.com/a/6190500/562769
    def __init__(self, default_factory=None, *a, **kw):
        if default_factory is not None and not callable(default_factory):
            raise TypeError("first argument must be callable")
        OrderedDict.__init__(self, *a, **kw)
        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return OrderedDict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)

    def __missing__(self, key):
        if self.default_factory is None:
            return MFileErrorClass(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        args = () if self.default_factory is None else (self.default_factory,)
        return type(self), args, None, None, self.items()

    def copy(self):
        return copy.copy(self)

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        return type(self)(self.default_factory, copy.deepcopy(self.items()))

    def __repr__(self):
        return (
            f"OrderedDefaultDict({self.default_factory}, {OrderedDict.__repr__(self)})"
        )

default_factory = default_factory instance-attribute

copy()

Source code in process/core/io/mfile/base.py
170
171
def copy(self):
    return copy.copy(self)

MFile

Class object to store the MFile Objects

Source code in process/core/io/mfile/base.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
class MFile:
    """Class object to store the MFile Objects"""

    def __init__(self, filename="MFILE.DAT"):
        self.filename = Path(filename)
        self.data = DefaultOrderedDict()
        self.mfile_lines = []
        self.mfile_modules = {}
        self.des_name = []
        self.mfile_modules["Misc"] = []
        self.current_module = "Misc"
        if filename is not None:
            self.open_mfile()
            self.parse_mfile()

    def get_variables(self, *variables: str, scan: int = -1) -> list[Any]:
        """Get a number of variables from a single scan

        Parameters
        ----------
        *variables:

        scan:
             (Default value = -1)
        """
        return [self.get(v, scan=scan) for v in variables]

    def get(self, variable: str, *, scan: int = -1) -> Any:
        """Get variable data from a given scan

        Parameters
        ----------
        variable:

        scan:
             (Default value = -1)
        """
        return self.data[variable].get_scan(scan)

    def open_mfile(self):
        """Function to open MFILE.DAT"""
        if not self.filename.is_file():
            raise FileNotFoundError(f"MFile '{self.filename}' doesn't exist")

        with open(self.filename, encoding="utf-8") as mfile:
            self.mfile_lines = mfile.readlines()

        for i in range(len(self.mfile_lines)):
            if "*----" in self.mfile_lines[i] or "***" in self.mfile_lines[i]:
                self.mfile_lines = self.mfile_lines[:i]
                self.mfile_end = i
                return

    def parse_mfile(self):
        """Function to parse MFILE.DAT"""
        for line in (
            c for c in (clean_line(lines) for lines in self.mfile_lines) if c != [""]
        ):
            self.add_line(line)

    def add_line(self, line):
        """Function to read the line from MFILE and add to the appropriate
        class or create a new class if it is the first instance of it.

        Parameters
        ----------
        line :

        """
        if "#" in line[:2]:
            combined = " ".join(line[1:-1])
            exclusions = [
                "Feasible",
                "feasible",
                "Errors",
                "Waveforms",
                "Power Reactor Optimisation Code",
            ]
            if any(exclusion not in combined for exclusion in exclusions):
                self.current_module = combined
                self.mfile_modules[self.current_module] = []

        else:
            var_des = line[0]
            extracted_var_name = sort_brackets(line[1])

            if extracted_var_name == "":
                var_name = var_des
                self.des_name.append(var_name)
            else:
                var_name = extracted_var_name

            if "runtitle" in var_name:
                var_value = " ".join(line[2:])
            else:
                # Pass all value "words"
                var_value = sort_value(line[2:])
            var_unit = get_unit(var_des)
            var_flag = line[3] if len(line) >= 4 else None

            self.mfile_modules[self.current_module].append(var_name)
            self.add_to_mfile_variable(var_des, var_name, var_value, var_unit, var_flag)

    def add_to_mfile_variable(self, des, name, value, unit, flag, scan=None):
        """Function to add value to MFile class for that name/description

        Parameters
        ----------
        des :

        name :

        value :

        unit :

        flag :

        scan :
             (Default value = None)
        """
        var_key = des.lower().replace("_", " ") if name == "" else name.lower()

        if var_key in self.data:
            scan_num = scan or (self.data[var_key].get_number_of_scans() + 1)

            # Check for duplicate entries per scan point if there are scans and no scans
            a = len(self.data[var_key].get_scans())
            b = len(self.data["iscan"].get_scans()) if "iscan" in self.data else 1

            if var_key != "iscan":
                if a < b:
                    self.data[var_key].set_scan(scan_num, value)
            else:
                self.data[var_key].set_scan(scan_num, value)
        else:
            var = MFileVariable(
                name, des, unit, var_flag=flag, var_mod=self.current_module
            )
            self.data[var_key] = var
            self.data[var_key].set_scan(1, value)

    def to_dict(self, keys=None, scan: int | None = -1, verbose=False) -> dict:
        """Convert MFile to dictionary

        Parameters
        ----------
        keys :
             keys to select
        scan :
             scan to select
        verbose :
             verbosity of output
        """
        if keys is None:
            keys = self.data.keys()

        def _get_data(item, dat_key):
            data = self.data[item].get_scan(dat_key)
            des = self.data[item].var_description.replace("_", " ")
            return {"value": data, "description": des} if verbose else data

        save_range = (
            range(1, self.data["rmajor"].get_number_of_scans() + 1)
            if scan is None
            else [scan]
        )
        output = {
            f"scan-{i + 1}": {
                item: _get_data(
                    item, -1 if self.data[item].get_number_of_scans() == 1 else i
                )
                for item in keys
            }
            for i in save_range
        }
        return (
            output[next(iter(output.keys()))]
            if len(output.keys()) == 1 and scan is not None
            else output
        )

    def to_json(
        self,
        filename: Path | None = None,
        keys_to_write=None,
        scan: int | None = -1,
        verbose=False,
    ):
        """Write MFILE object to JSON file

        Parameters
        ----------
        keys_to_write :
             keys to select
        scan :
             scan to select
        verbose :
             verbosity of output
        """
        with open(filename or f"{self.filename}.json", "w") as fp:
            json.dump(self.to_dict(keys_to_write, scan, verbose), fp, indent=4)

    def to_toml(
        self,
        filename: Path | None = None,
        keys_to_write=None,
        scan: int | None = -1,
        verbose=False,
    ):
        """Write MFILE object to JSON file

        Parameters
        ----------
        keys_to_write :
             keys to select
        scan :
             scan to select
        verbose :
             verbosity of output
        """
        import toml  # noqa:PLC0415

        with open(filename or f"{self.filename}.toml", "w") as file:
            toml.dump(self.to_dict(keys_to_write, scan, verbose), file)

    def to_csv(
        self,
        filename: Path | None = None,
        keys_to_write=None,
        scan=-1,
        verbose=False,
    ):
        """Write to csv file.

        Parameters
        ----------
        args : string, list of tuples
            input filename, variable data
        csv_outfile :

        output_data :
             (Default value = None)
        """
        columns = (
            ("Description", "Varname", "Value") if verbose else ("Varname", "Value")
        )
        fmt = ("%s", "%s", "%.5e") if verbose else ("%s", "%.5e")

        def save(filename, output_data, header):
            np.savetxt(
                filename,
                np.asarray(output_data or [], dtype=object),
                fmt=fmt,
                delimiter=",",
                header=header,
                footer="",
                comments="",
            )

        def get_cells(k, v):
            if verbose:
                return [v["description"], k, v["value"]]
            return [k, v]

        if scan is None:
            with open(filename or f"{self.filename}.csv", "w") as f:
                f.write("PROCESS MFILE converted to csv\n")
                for scan_key, vals in self.to_dict(
                    keys_to_write, scan=scan, verbose=verbose
                ).items():
                    header = f"{scan_key}\n" + ",".join(columns)
                    output_data = list(starmap(get_cells, vals.items()))
                    save(f, output_data, header)
        else:
            output_data = list(
                starmap(
                    get_cells,
                    self.to_dict(keys_to_write, scan=scan, verbose=verbose).items(),
                )
            )
            header = "PROCESS MFILE converted to csv\n" + ",".join(columns)
            save(filename or f"{self.filename}.csv", output_data, header)

filename = Path(filename) instance-attribute

data = DefaultOrderedDict() instance-attribute

mfile_lines = [] instance-attribute

mfile_modules = {} instance-attribute

des_name = [] instance-attribute

current_module = 'Misc' instance-attribute

get_variables(*variables, scan=-1)

Get a number of variables from a single scan

Parameters:

Name Type Description Default
*variables str
()
scan int

(Default value = -1)

-1
Source code in process/core/io/mfile/base.py
200
201
202
203
204
205
206
207
208
209
210
def get_variables(self, *variables: str, scan: int = -1) -> list[Any]:
    """Get a number of variables from a single scan

    Parameters
    ----------
    *variables:

    scan:
         (Default value = -1)
    """
    return [self.get(v, scan=scan) for v in variables]

get(variable, *, scan=-1)

Get variable data from a given scan

Parameters:

Name Type Description Default
variable str
required
scan int

(Default value = -1)

-1
Source code in process/core/io/mfile/base.py
212
213
214
215
216
217
218
219
220
221
222
def get(self, variable: str, *, scan: int = -1) -> Any:
    """Get variable data from a given scan

    Parameters
    ----------
    variable:

    scan:
         (Default value = -1)
    """
    return self.data[variable].get_scan(scan)

open_mfile()

Function to open MFILE.DAT

Source code in process/core/io/mfile/base.py
224
225
226
227
228
229
230
231
232
233
234
235
236
def open_mfile(self):
    """Function to open MFILE.DAT"""
    if not self.filename.is_file():
        raise FileNotFoundError(f"MFile '{self.filename}' doesn't exist")

    with open(self.filename, encoding="utf-8") as mfile:
        self.mfile_lines = mfile.readlines()

    for i in range(len(self.mfile_lines)):
        if "*----" in self.mfile_lines[i] or "***" in self.mfile_lines[i]:
            self.mfile_lines = self.mfile_lines[:i]
            self.mfile_end = i
            return

parse_mfile()

Function to parse MFILE.DAT

Source code in process/core/io/mfile/base.py
238
239
240
241
242
243
def parse_mfile(self):
    """Function to parse MFILE.DAT"""
    for line in (
        c for c in (clean_line(lines) for lines in self.mfile_lines) if c != [""]
    ):
        self.add_line(line)

add_line(line)

Function to read the line from MFILE and add to the appropriate class or create a new class if it is the first instance of it.

Parameters:

Name Type Description Default
line
required
Source code in process/core/io/mfile/base.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def add_line(self, line):
    """Function to read the line from MFILE and add to the appropriate
    class or create a new class if it is the first instance of it.

    Parameters
    ----------
    line :

    """
    if "#" in line[:2]:
        combined = " ".join(line[1:-1])
        exclusions = [
            "Feasible",
            "feasible",
            "Errors",
            "Waveforms",
            "Power Reactor Optimisation Code",
        ]
        if any(exclusion not in combined for exclusion in exclusions):
            self.current_module = combined
            self.mfile_modules[self.current_module] = []

    else:
        var_des = line[0]
        extracted_var_name = sort_brackets(line[1])

        if extracted_var_name == "":
            var_name = var_des
            self.des_name.append(var_name)
        else:
            var_name = extracted_var_name

        if "runtitle" in var_name:
            var_value = " ".join(line[2:])
        else:
            # Pass all value "words"
            var_value = sort_value(line[2:])
        var_unit = get_unit(var_des)
        var_flag = line[3] if len(line) >= 4 else None

        self.mfile_modules[self.current_module].append(var_name)
        self.add_to_mfile_variable(var_des, var_name, var_value, var_unit, var_flag)

add_to_mfile_variable(des, name, value, unit, flag, scan=None)

Function to add value to MFile class for that name/description

Parameters:

Name Type Description Default
des
required
name
required
value
required
unit
required
flag
required
scan

(Default value = None)

None
Source code in process/core/io/mfile/base.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
def add_to_mfile_variable(self, des, name, value, unit, flag, scan=None):
    """Function to add value to MFile class for that name/description

    Parameters
    ----------
    des :

    name :

    value :

    unit :

    flag :

    scan :
         (Default value = None)
    """
    var_key = des.lower().replace("_", " ") if name == "" else name.lower()

    if var_key in self.data:
        scan_num = scan or (self.data[var_key].get_number_of_scans() + 1)

        # Check for duplicate entries per scan point if there are scans and no scans
        a = len(self.data[var_key].get_scans())
        b = len(self.data["iscan"].get_scans()) if "iscan" in self.data else 1

        if var_key != "iscan":
            if a < b:
                self.data[var_key].set_scan(scan_num, value)
        else:
            self.data[var_key].set_scan(scan_num, value)
    else:
        var = MFileVariable(
            name, des, unit, var_flag=flag, var_mod=self.current_module
        )
        self.data[var_key] = var
        self.data[var_key].set_scan(1, value)

to_dict(keys=None, scan=-1, verbose=False)

Convert MFile to dictionary

Parameters:

Name Type Description Default
keys

keys to select

None
scan int | None

scan to select

-1
verbose

verbosity of output

False
Source code in process/core/io/mfile/base.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
def to_dict(self, keys=None, scan: int | None = -1, verbose=False) -> dict:
    """Convert MFile to dictionary

    Parameters
    ----------
    keys :
         keys to select
    scan :
         scan to select
    verbose :
         verbosity of output
    """
    if keys is None:
        keys = self.data.keys()

    def _get_data(item, dat_key):
        data = self.data[item].get_scan(dat_key)
        des = self.data[item].var_description.replace("_", " ")
        return {"value": data, "description": des} if verbose else data

    save_range = (
        range(1, self.data["rmajor"].get_number_of_scans() + 1)
        if scan is None
        else [scan]
    )
    output = {
        f"scan-{i + 1}": {
            item: _get_data(
                item, -1 if self.data[item].get_number_of_scans() == 1 else i
            )
            for item in keys
        }
        for i in save_range
    }
    return (
        output[next(iter(output.keys()))]
        if len(output.keys()) == 1 and scan is not None
        else output
    )

to_json(filename=None, keys_to_write=None, scan=-1, verbose=False)

Write MFILE object to JSON file

Parameters:

Name Type Description Default
keys_to_write

keys to select

None
scan int | None

scan to select

-1
verbose

verbosity of output

False
Source code in process/core/io/mfile/base.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
def to_json(
    self,
    filename: Path | None = None,
    keys_to_write=None,
    scan: int | None = -1,
    verbose=False,
):
    """Write MFILE object to JSON file

    Parameters
    ----------
    keys_to_write :
         keys to select
    scan :
         scan to select
    verbose :
         verbosity of output
    """
    with open(filename or f"{self.filename}.json", "w") as fp:
        json.dump(self.to_dict(keys_to_write, scan, verbose), fp, indent=4)

to_toml(filename=None, keys_to_write=None, scan=-1, verbose=False)

Write MFILE object to JSON file

Parameters:

Name Type Description Default
keys_to_write

keys to select

None
scan int | None

scan to select

-1
verbose

verbosity of output

False
Source code in process/core/io/mfile/base.py
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
def to_toml(
    self,
    filename: Path | None = None,
    keys_to_write=None,
    scan: int | None = -1,
    verbose=False,
):
    """Write MFILE object to JSON file

    Parameters
    ----------
    keys_to_write :
         keys to select
    scan :
         scan to select
    verbose :
         verbosity of output
    """
    import toml  # noqa:PLC0415

    with open(filename or f"{self.filename}.toml", "w") as file:
        toml.dump(self.to_dict(keys_to_write, scan, verbose), file)

to_csv(filename=None, keys_to_write=None, scan=-1, verbose=False)

Write to csv file.

Parameters:

Name Type Description Default
args string, list of tuples

input filename, variable data

required
csv_outfile
required
output_data

(Default value = None)

required
Source code in process/core/io/mfile/base.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def to_csv(
    self,
    filename: Path | None = None,
    keys_to_write=None,
    scan=-1,
    verbose=False,
):
    """Write to csv file.

    Parameters
    ----------
    args : string, list of tuples
        input filename, variable data
    csv_outfile :

    output_data :
         (Default value = None)
    """
    columns = (
        ("Description", "Varname", "Value") if verbose else ("Varname", "Value")
    )
    fmt = ("%s", "%s", "%.5e") if verbose else ("%s", "%.5e")

    def save(filename, output_data, header):
        np.savetxt(
            filename,
            np.asarray(output_data or [], dtype=object),
            fmt=fmt,
            delimiter=",",
            header=header,
            footer="",
            comments="",
        )

    def get_cells(k, v):
        if verbose:
            return [v["description"], k, v["value"]]
        return [k, v]

    if scan is None:
        with open(filename or f"{self.filename}.csv", "w") as f:
            f.write("PROCESS MFILE converted to csv\n")
            for scan_key, vals in self.to_dict(
                keys_to_write, scan=scan, verbose=verbose
            ).items():
                header = f"{scan_key}\n" + ",".join(columns)
                output_data = list(starmap(get_cells, vals.items()))
                save(f, output_data, header)
    else:
        output_data = list(
            starmap(
                get_cells,
                self.to_dict(keys_to_write, scan=scan, verbose=verbose).items(),
            )
        )
        header = "PROCESS MFILE converted to csv\n" + ",".join(columns)
        save(filename or f"{self.filename}.csv", output_data, header)

sort_value(value_words)

Parse value section of a line in MFILE.

value_words is a list of strings, which is then parsed.

Parameters:

Name Type Description Default
value_words List[str]

value of var in MFILE as list of strings

required
value_words list[str]
required

Returns:

Type Description
Union[str, float]

string or float representation of value list

Source code in process/core/io/mfile/base.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
def sort_value(value_words: list[str]) -> str | float:
    """Parse value section of a line in MFILE.

    value_words is a list of strings, which is then parsed.

    Parameters
    ----------
    value_words : List[str]
        value of var in MFILE as list of strings
    value_words: list[str] :


    Returns
    -------
    Union[str, float]
        string or float representation of value list
    """
    if any(c in value_words[0] for c in ['"', "'"]):
        # First "word" begins with ": return words as single str
        return " ".join(value_words).strip().strip('"').strip()
    try:
        # Attempt float conversion of first word
        return float(value_words[0])
    except ValueError:
        # Log the exception with details
        logger.exception("Can't parse value in MFILE: %s", value_words)
        # Return the original string as a fallback
        return " ".join(value_words).strip()

sort_brackets(var)

Function to sort bracket madness on variable name.

Parameters:

Name Type Description Default
var
required
Source code in process/core/io/mfile/base.py
500
501
502
503
504
505
506
507
508
509
510
511
512
513
def sort_brackets(var):
    """Function to sort bracket madness on variable name.

    Parameters
    ----------
    var :

    """
    if var != "":
        tmp_name = var.lstrip("(").split(")")
        if len(tmp_name) > 2:
            return tmp_name[0] + ")"
        return tmp_name[0]
    return ""

clean_line(line)

Cleans an MFILE line into the three parts we care about

Parameters:

Name Type Description Default
line
required
Source code in process/core/io/mfile/base.py
516
517
518
519
520
521
522
523
524
def clean_line(line):
    """Cleans an MFILE line into the three parts we care about

    Parameters
    ----------
    line :

    """
    return [item.strip("_ \n") for item in line.split(" ") if item != ""]

search_keys(dictionary, variable)

Searches the dictionary keys for matches to the variable name 'variable' in arguments.

Puts everything into lower case before searching.

Parameters:

Name Type Description Default
dictionary

dictionary to search in

required
variable

variable name to search for

required

Returns:

Type Description

List of matches to the searched for variable

Source code in process/core/io/mfile/base.py
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
def search_keys(dictionary, variable):
    """Searches the dictionary keys for matches to the variable name
    'variable' in arguments.

    Puts everything into lower case before searching.

    Parameters
    ----------
    dictionary :
        dictionary to search in
    variable :
        variable name to search for

    Returns
    -------
    :
        List of matches to the searched for variable
    """
    return [key for key in dictionary if variable.lower() in key.lower()]

search_des(dictionary, description)

Searches the dictionary descriptions for matches to the description 'description' from the arguments.

Puts everything into lower case before searching.

Parameters:

Name Type Description Default
dictionary

dictionary to search in

required
variable

variable name to search for

required

Returns:

Type Description

List of matches to the searched for description

Source code in process/core/io/mfile/base.py
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
def search_des(dictionary, description):
    """Searches the dictionary descriptions for matches to the description
    'description' from the arguments.

    Puts everything into lower case before searching.

    Parameters
    ----------
    dictionary :
        dictionary to search in
    variable :
        variable name to search for

    Returns
    -------
    :
        List of matches to the searched for description
    """
    descriptions = [dictionary[key].var_descript.lower() for key in dictionary.data]
    return [item for item in descriptions if description.lower() in item.lower()]

get_unit(variable_desc)

Returns the unit from a variable description if possible, else None.

Parameters:

Name Type Description Default
variable_desc
required
Source code in process/core/io/mfile/base.py
570
571
572
573
574
575
576
577
578
579
580
581
def get_unit(variable_desc):
    """Returns the unit from a variable description if possible, else None.

    Parameters
    ----------
    variable_desc :

    """
    candidate = variable_desc.rsplit("_", 1)[-1]
    if candidate.startswith("(") and candidate.endswith(")"):
        return candidate[1:-1]
    return None

get_mfile_initial_ixc_values(file_path)

Initialise the input file and obtain the initial values of the iteration variables

Parameters:

Name Type Description Default
file_path Path

The path to the MFile to get the initial iteration variable values from.

required
Notes

This method initialises a SingleRun. At present, this involves mutating the global data structure so it is not safe to run this method during a PROCESS run.

Source code in process/core/io/mfile/base.py
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
def get_mfile_initial_ixc_values(file_path: Path):
    """Initialise the input file and obtain the initial values of the iteration variables

    Parameters
    ----------
    file_path :
        The path to the MFile to get the initial iteration variable values from.

    Notes
    -----
    This method initialises a SingleRun. At present, this involves mutating the global
    data structure so it is not safe to run this method during a PROCESS run.
    """
    from process.main import SingleRun  # noqa:PLC0415

    SingleRun(file_path.as_posix())
    iteration_variables.load_iteration_variables()

    iteration_variable_names = []
    iteration_variable_values = []

    for i in range(data_structure.numerics.nvar):
        ivar = data_structure.numerics.ixc[i].item()

        itv = iteration_variables.ITERATION_VARIABLES[ivar]

        iteration_variable_names.append(itv.name)
        if array := re.match(r"(\w+)\(([0-9]+)\)", itv.name):
            var_name = array.group(1)
            index = array.group(2)
            iteration_variable_values.append(
                getattr(itv.module, var_name)[int(index) - 1]
            )
        else:
            iteration_variable_values.append(getattr(itv.module, itv.name))

    return iteration_variable_names, iteration_variable_values