crunchflow 2.0.2__tar.gz → 2.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {crunchflow-2.0.2 → crunchflow-2.0.4}/PKG-INFO +1 -1
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/input/blocks.py +39 -4
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/input/inputfile.py +35 -10
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/output/spatial.py +109 -20
- {crunchflow-2.0.2 → crunchflow-2.0.4}/pyproject.toml +1 -1
- {crunchflow-2.0.2 → crunchflow-2.0.4}/LICENSE +0 -0
- {crunchflow-2.0.2 → crunchflow-2.0.4}/README.md +0 -0
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/__init__.py +0 -0
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/input/__init__.py +0 -0
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/output/__init__.py +0 -0
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/output/timeseries.py +0 -0
- {crunchflow-2.0.2 → crunchflow-2.0.4}/crunchflow/util.py +0 -0
|
@@ -62,7 +62,7 @@ class Runtime(KeywordBlock):
|
|
|
62
62
|
self.coordinate = ''
|
|
63
63
|
self.coordinates = ''
|
|
64
64
|
self.correction_max = ''
|
|
65
|
-
self.
|
|
65
|
+
self.courant_number = ''
|
|
66
66
|
self.database = ''
|
|
67
67
|
self.database_sweep = ''
|
|
68
68
|
self.debye_huckel = ''
|
|
@@ -476,7 +476,7 @@ class Gases(SpeciesBlock):
|
|
|
476
476
|
pass
|
|
477
477
|
|
|
478
478
|
|
|
479
|
-
class
|
|
479
|
+
class SurfaceComplexation(KeywordBlock):
|
|
480
480
|
def __init__(self):
|
|
481
481
|
super().__init__()
|
|
482
482
|
self.species = []
|
|
@@ -494,8 +494,43 @@ class KineticsBlock(KeywordBlock):
|
|
|
494
494
|
return "\n".join(result)
|
|
495
495
|
|
|
496
496
|
|
|
497
|
-
class
|
|
498
|
-
|
|
497
|
+
class KineticsBlock(KeywordBlock):
|
|
498
|
+
def __init__(self):
|
|
499
|
+
super().__init__()
|
|
500
|
+
self.species_dict = {}
|
|
501
|
+
self.species = []
|
|
502
|
+
|
|
503
|
+
def set_parameters(self, parameters):
|
|
504
|
+
for species, details in parameters.items():
|
|
505
|
+
label = details.pop('label', None)
|
|
506
|
+
if species not in self.species_dict:
|
|
507
|
+
self.species_dict[species] = {}
|
|
508
|
+
self.species.append(species)
|
|
509
|
+
if label:
|
|
510
|
+
self.species_dict[species][label] = details
|
|
511
|
+
else:
|
|
512
|
+
self.species_dict[species]['default'] = details
|
|
513
|
+
|
|
514
|
+
def update_parameters(self, species, label, new_details):
|
|
515
|
+
if species in self.species_dict and label in self.species_dict[species]:
|
|
516
|
+
self.species_dict[species][label].update(new_details)
|
|
517
|
+
else:
|
|
518
|
+
print(f"Species '{species}' with label '{label}' not found.")
|
|
519
|
+
|
|
520
|
+
def __str__(self):
|
|
521
|
+
result = []
|
|
522
|
+
for species, labels in self.species_dict.items():
|
|
523
|
+
if 'default' in labels and not labels['default']:
|
|
524
|
+
result.append(f"{species:<20}")
|
|
525
|
+
else:
|
|
526
|
+
for label, details in labels.items():
|
|
527
|
+
if label == 'default' and not details:
|
|
528
|
+
result.append(f"{species:<20}")
|
|
529
|
+
else:
|
|
530
|
+
details_str = ' '.join(f'-{k} {v}' for k, v in details.items())
|
|
531
|
+
label_str = f"-label {label} " if label != 'default' else ""
|
|
532
|
+
result.append(f"{species:<20} {label_str}{details_str}")
|
|
533
|
+
return "\n".join(result)
|
|
499
534
|
|
|
500
535
|
|
|
501
536
|
class Minerals(KineticsBlock):
|
|
@@ -63,7 +63,7 @@ class InputFile:
|
|
|
63
63
|
# Initialize other blocks as needed
|
|
64
64
|
|
|
65
65
|
def set_block(self, block_name, parameters):
|
|
66
|
-
"""Set the parameters
|
|
66
|
+
"""Set the parameters of a block in an InputFile instance.
|
|
67
67
|
|
|
68
68
|
Parameters
|
|
69
69
|
----------
|
|
@@ -93,11 +93,9 @@ class InputFile:
|
|
|
93
93
|
# Note that the "CONDITION <name>" is included in
|
|
94
94
|
# Condition.__str__ method, so it is omitted here
|
|
95
95
|
result.append(f"{str(condition)}\nEND")
|
|
96
|
-
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
# block_name = format_class_name(item.__class__.__name__)
|
|
100
|
-
# result.append(f"{block_name}\n{str(item)}\nEND")
|
|
96
|
+
|
|
97
|
+
# Otherwise, check if the block has any attributes set
|
|
98
|
+
# and if so, format the block name then print it
|
|
101
99
|
elif any(val for val in value.__dict__.values() if val):
|
|
102
100
|
block_name = format_class_name(value.__class__.__name__)
|
|
103
101
|
result.append(f"{block_name}\n{str(value)}\nEND")
|
|
@@ -114,7 +112,12 @@ class InputFile:
|
|
|
114
112
|
The path to the output file. Default is the current directory.
|
|
115
113
|
update_pestcontrol : bool, optional
|
|
116
114
|
Whether to update PestControl.ant with the name of the
|
|
117
|
-
CrunchFlow input file. Default is False.
|
|
115
|
+
CrunchFlow input file. Default is False.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
None
|
|
120
|
+
The CrunchFlow input file is saved to disk."""
|
|
118
121
|
|
|
119
122
|
full_path = os.path.join(path, filename)
|
|
120
123
|
with open(full_path, 'w') as file:
|
|
@@ -127,7 +130,7 @@ class InputFile:
|
|
|
127
130
|
if update_pestcontrol:
|
|
128
131
|
folder = os.path.dirname(full_path)
|
|
129
132
|
pestfile = os.path.join(folder, 'PestControl.ant')
|
|
130
|
-
with open(
|
|
133
|
+
with open(pestfile, 'w') as file:
|
|
131
134
|
file.write('%s \n' % os.path.basename(full_path))
|
|
132
135
|
|
|
133
136
|
@classmethod
|
|
@@ -160,7 +163,7 @@ class InputFile:
|
|
|
160
163
|
# Define attributes and blocks to be handled as special cases
|
|
161
164
|
# SpeciesBlock and KineticsBlock instances are handled differently below
|
|
162
165
|
species_blocks = ['primary_species', 'secondary_species', 'gases']
|
|
163
|
-
kinetics_blocks = ['minerals', 'aqueous_kinetics'
|
|
166
|
+
kinetics_blocks = ['minerals', 'aqueous_kinetics']
|
|
164
167
|
|
|
165
168
|
# Some attributes can be set multiple times within a single block
|
|
166
169
|
multiply_defined = ['time_series', 'pressure', 'mineral',
|
|
@@ -229,13 +232,35 @@ class InputFile:
|
|
|
229
232
|
parts = line.split()
|
|
230
233
|
current_block.species.append(parts[0])
|
|
231
234
|
|
|
232
|
-
elif current_block_name
|
|
235
|
+
elif current_block_name == 'surface_complexation':
|
|
233
236
|
parts = line.split()
|
|
234
237
|
species = parts[0]
|
|
235
238
|
details = " ".join(parts[1:]) if len(parts) > 1 else ""
|
|
236
239
|
current_block.species.append(species)
|
|
237
240
|
current_block.species_dict[species] = details
|
|
238
241
|
|
|
242
|
+
elif current_block_name in kinetics_blocks:
|
|
243
|
+
parts = line.split()
|
|
244
|
+
species = parts[0]
|
|
245
|
+
details = {}
|
|
246
|
+
|
|
247
|
+
# Assume the default label
|
|
248
|
+
# This will be updated below if it is included in details
|
|
249
|
+
label = 'default'
|
|
250
|
+
|
|
251
|
+
# If there are details associated with the species_dict, then parse them
|
|
252
|
+
if len(parts) > 1:
|
|
253
|
+
# Loop through kinetic options (e.g., -activation, -rate, etc.)
|
|
254
|
+
# and store this information in a dictionary
|
|
255
|
+
for i in range(1, len(parts), 2):
|
|
256
|
+
key = parts[i].lstrip('-')
|
|
257
|
+
value = parts[i+1] if (i+1) < len(parts) else None
|
|
258
|
+
if key == 'label':
|
|
259
|
+
label = value
|
|
260
|
+
else:
|
|
261
|
+
details[key] = value
|
|
262
|
+
current_block.set_parameters({species: {**details, 'label': label}})
|
|
263
|
+
|
|
239
264
|
# All other blocks, split on whitespace
|
|
240
265
|
else:
|
|
241
266
|
parts = line.split()
|
|
@@ -42,21 +42,24 @@ def get_tec_metadata(file, folder='.'):
|
|
|
42
42
|
# Open file and read line by line
|
|
43
43
|
inpath = os.path.join(folder, file)
|
|
44
44
|
with open(inpath) as f:
|
|
45
|
+
lines = f.readlines()
|
|
46
|
+
|
|
45
47
|
# Read the first line to determine the format of the file
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
title = line.split('"')[1]
|
|
48
|
+
if "TITLE" in lines[0]:
|
|
49
|
+
title = lines[0].split('"')[1]
|
|
49
50
|
fmt = '.tec'
|
|
50
|
-
elif 'Time' in
|
|
51
|
+
elif 'Time' in lines[0]:
|
|
51
52
|
title = ''
|
|
52
53
|
fmt = '.out'
|
|
54
|
+
elif 'Units' in lines[0]:
|
|
55
|
+
title = ''
|
|
56
|
+
fmt = '.dat'
|
|
53
57
|
else:
|
|
54
58
|
raise ValueError("Could not determine the format of this file")
|
|
55
59
|
|
|
56
60
|
# Read the rest of the header
|
|
57
61
|
if fmt == '.tec':
|
|
58
|
-
|
|
59
|
-
columns = line.split('"')
|
|
62
|
+
columns = lines[1].split('"')
|
|
60
63
|
|
|
61
64
|
# Remove x, y, z
|
|
62
65
|
columns = [col.strip() for col in columns]
|
|
@@ -70,11 +73,14 @@ def get_tec_metadata(file, folder='.'):
|
|
|
70
73
|
for col in remove_fields:
|
|
71
74
|
columns.remove(col)
|
|
72
75
|
elif fmt == '.out':
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
# pH files do not have a units line
|
|
77
|
+
if 'pH' in file:
|
|
78
|
+
columns_line = lines[1] # skip units line
|
|
79
|
+
else:
|
|
80
|
+
columns_line = lines[2]
|
|
75
81
|
|
|
76
82
|
# Split on whitespace
|
|
77
|
-
raw_cols =
|
|
83
|
+
raw_cols = columns_line.split()
|
|
78
84
|
|
|
79
85
|
# Some versions of CrunchFlow don't include column names
|
|
80
86
|
# in this case, the column entries should be numeric and we'll
|
|
@@ -84,6 +90,35 @@ def get_tec_metadata(file, folder='.'):
|
|
|
84
90
|
else:
|
|
85
91
|
columns = raw_cols
|
|
86
92
|
|
|
93
|
+
elif fmt == '.dat':
|
|
94
|
+
title = lines[1].split('"')[1]
|
|
95
|
+
columns = lines[2].split('"')
|
|
96
|
+
|
|
97
|
+
# Remove x, y, z
|
|
98
|
+
columns = [col.strip() for col in columns]
|
|
99
|
+
for col in ['VARIABLES =']:
|
|
100
|
+
if col in columns:
|
|
101
|
+
columns.remove(col)
|
|
102
|
+
|
|
103
|
+
# Remove commas, which can be repeated so will not be removed with loop above
|
|
104
|
+
columns = [col.replace(',', '') for col in columns]
|
|
105
|
+
|
|
106
|
+
# Remove whitespace, which was reduced to len 0 via col.strip()
|
|
107
|
+
remove_fields = [col for col in columns if len(col) == 0]
|
|
108
|
+
for col in remove_fields:
|
|
109
|
+
columns.remove(col)
|
|
110
|
+
|
|
111
|
+
# With .dat files, the naming scheme for x, y and z varies
|
|
112
|
+
# depending on the spatial units
|
|
113
|
+
if len(lines[3].split(',')) == 3:
|
|
114
|
+
# then this is 2d, so remove first two columns
|
|
115
|
+
columns = columns[2:]
|
|
116
|
+
elif len(lines[3].split(',')) == 4:
|
|
117
|
+
# then this is 3d, so remove first three columns
|
|
118
|
+
columns = columns[3:]
|
|
119
|
+
else:
|
|
120
|
+
raise ValueError("Could not determine the format of this file")
|
|
121
|
+
|
|
87
122
|
return columns, title, fmt
|
|
88
123
|
|
|
89
124
|
|
|
@@ -197,6 +232,29 @@ class SpatialProfile:
|
|
|
197
232
|
Get line segments that outline all the regions equal to
|
|
198
233
|
the provided value.
|
|
199
234
|
|
|
235
|
+
__init__(fileprefix, folder, output_times, suffix)
|
|
236
|
+
Read in and get basic info about all .tec files matching `fileprefix`.
|
|
237
|
+
For example, `tec('volume')` will read in all files matching
|
|
238
|
+
'volume[0-9]+.tec'
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
fileprefix : str
|
|
243
|
+
file prefix of the tec file to read in, without the timestep
|
|
244
|
+
number or ".tec" file ending. For example, if your files are
|
|
245
|
+
"volume1.tec", "volume2.tec", etc., then fileprefix should be
|
|
246
|
+
"volume".
|
|
247
|
+
folder : str, optional
|
|
248
|
+
folder in which the .tec files are located. The default is the
|
|
249
|
+
current directory
|
|
250
|
+
output_times : list of float, optional
|
|
251
|
+
list of the actual output times at which the .tec files were
|
|
252
|
+
output, in CrunchFlow time units
|
|
253
|
+
suffix : str, optional
|
|
254
|
+
file ending of the tec files to read in. This can vary depending on the
|
|
255
|
+
version of CrunchTope used. The default is '.tec', but '.out' is also
|
|
256
|
+
tried if '.tec' files are not found.
|
|
257
|
+
|
|
200
258
|
Examples
|
|
201
259
|
--------
|
|
202
260
|
>>> vol = SpatialProfile('volume')
|
|
@@ -207,7 +265,7 @@ class SpatialProfile:
|
|
|
207
265
|
|
|
208
266
|
def __init__(self, fileprefix, folder='.', output_times=None, suffix='.tec'):
|
|
209
267
|
"""Read in and get basic info about all .tec files matching `fileprefix`.
|
|
210
|
-
For example, `
|
|
268
|
+
For example, `SpatialProfile('volume')` will read in all files matching
|
|
211
269
|
'volume[0-9]+.tec'
|
|
212
270
|
|
|
213
271
|
Parameters
|
|
@@ -217,27 +275,30 @@ class SpatialProfile:
|
|
|
217
275
|
number or ".tec" file ending. For example, if your files are
|
|
218
276
|
"volume1.tec", "volume2.tec", etc., then fileprefix should be
|
|
219
277
|
"volume".
|
|
220
|
-
folder : str
|
|
278
|
+
folder : str, optional
|
|
221
279
|
folder in which the .tec files are located. The default is the
|
|
222
280
|
current directory
|
|
223
|
-
output_times : list of float
|
|
281
|
+
output_times : list of float, optional
|
|
224
282
|
list of the actual output times at which the .tec files were
|
|
225
283
|
output, in CrunchFlow time units
|
|
226
|
-
suffix : str
|
|
284
|
+
suffix : str, optional
|
|
227
285
|
file ending of the tec files to read in. This can vary depending on the
|
|
228
|
-
version of CrunchTope used. The default is '.tec', but '.out'
|
|
229
|
-
tried if '.tec' files are not found.
|
|
286
|
+
version of CrunchTope used. The default is '.tec', but '.out' and '.dat'
|
|
287
|
+
are also tried if '.tec' files are not found.
|
|
230
288
|
"""
|
|
231
289
|
|
|
232
290
|
# Glob doesn't support regex, so match manually using re and os
|
|
233
291
|
search_str = '^' + fileprefix + '[0-9]+%s' % suffix
|
|
234
292
|
unsort_files = [f for f in os.listdir(folder) if re.search(search_str, f)]
|
|
235
293
|
|
|
294
|
+
# Try again with .out or .dat suffix
|
|
236
295
|
if len(unsort_files) == 0:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
296
|
+
for try_suffix in ['.out', '.dat']:
|
|
297
|
+
search_str = '^' + fileprefix + '[0-9]+' + try_suffix
|
|
298
|
+
unsort_files = [f for f in os.listdir(folder) if re.search(search_str, f)]
|
|
299
|
+
if len(unsort_files) > 0:
|
|
300
|
+
suffix = try_suffix
|
|
301
|
+
break
|
|
241
302
|
|
|
242
303
|
if len(unsort_files) == 0:
|
|
243
304
|
raise ValueError("No files found matching %s. Double check the suffix and\n "
|
|
@@ -263,7 +324,7 @@ class SpatialProfile:
|
|
|
263
324
|
# Otherwise, try setting it by reading from each .out file
|
|
264
325
|
if self.fmt == '.out':
|
|
265
326
|
self.output_times = [get_out_output_time(f) for f in self.files]
|
|
266
|
-
elif self.fmt
|
|
327
|
+
elif self.fmt in ['.tec', '.dat']:
|
|
267
328
|
self.output_times = None
|
|
268
329
|
|
|
269
330
|
# Get the grid size
|
|
@@ -298,6 +359,24 @@ class SpatialProfile:
|
|
|
298
359
|
else:
|
|
299
360
|
skiprows = 3
|
|
300
361
|
self.coords = np.loadtxt(self.files[0], skiprows=skiprows, usecols=[0])
|
|
362
|
+
elif self.fmt == '.dat':
|
|
363
|
+
with open(self.files[0]) as f:
|
|
364
|
+
lines = f.readlines()
|
|
365
|
+
|
|
366
|
+
fields = lines[3].split(',')[1:]
|
|
367
|
+
if len(fields) == 2:
|
|
368
|
+
self.nx = int(re.sub('\\D', '', fields[0]))
|
|
369
|
+
self.ny = int(re.sub('\\D', '', fields[1]))
|
|
370
|
+
self.nz = np.nan
|
|
371
|
+
elif len(fields) == 3:
|
|
372
|
+
self.nx = int(re.sub('\\D', '', fields[0]))
|
|
373
|
+
self.ny = int(re.sub('\\D', '', fields[1]))
|
|
374
|
+
self.nz = int(re.sub('\\D', '', fields[2]))
|
|
375
|
+
else:
|
|
376
|
+
raise ValueError("Could not determine the coordinates in this file")
|
|
377
|
+
self.coords = np.loadtxt(self.files[0], skiprows=4, usecols=[0, 1])
|
|
378
|
+
self.griddedX = self.coords[:, 0].reshape(self.ny, self.nx)
|
|
379
|
+
self.griddedY = self.coords[:, 1].reshape(self.ny, self.nx)
|
|
301
380
|
|
|
302
381
|
def plot(self, var, time=None, plot_type='image', figsize=(12,3),
|
|
303
382
|
**kwargs):
|
|
@@ -561,6 +640,16 @@ class SpatialProfile:
|
|
|
561
640
|
skiprows = 3
|
|
562
641
|
col_no = self.columns.index(var)
|
|
563
642
|
data = np.loadtxt(self.files[time - 1], skiprows=skiprows, usecols=col_no)
|
|
643
|
+
elif self.fmt == '.dat':
|
|
644
|
+
if np.isnan(self.nz):
|
|
645
|
+
col_no = self.columns.index(var) + 2
|
|
646
|
+
data = np.loadtxt(self.files[time - 1], skiprows=4, usecols=col_no)
|
|
647
|
+
data = data.reshape(self.ny, self.nx)
|
|
648
|
+
else:
|
|
649
|
+
col_no = self.columns.index(var) + 3
|
|
650
|
+
data = np.loadtxt(self.files[time - 1], skiprows=4, usecols=col_no)
|
|
651
|
+
data = data.reshape(self.nz, self.ny, self.nx)
|
|
652
|
+
|
|
564
653
|
else:
|
|
565
654
|
raise ValueError("Could not determine the format of this file")
|
|
566
655
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|