forked from JADE-V-V/JADE
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmeshtal.py
286 lines (234 loc) · 9.2 KB
/
meshtal.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
183
184
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
# -*- coding: utf-8 -*-
"""
Created on Thu May 20 12:22:30 2021
@author: Davide Laghi
Copyright 2021, the JADE Development Team. All rights reserved.
This file is part of JADE.
JADE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
JADE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JADE. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import re
import pandas as pd
# PATTERNS
PAT_NUM = re.compile(r'(?<=Mesh Tally Number)\s+\d+') # blank spaces to be elim
PAT_DESC = re.compile(r'(?<=FMESH\s).+') # get the tally name
PAT_CYLYNDER = re.compile(r'\sR\s+Z\s') # Start of a cylyndrical tally
PAT_PARTICLE = re.compile(r'(?=<mesh tally).+')
# Meshtal to mctal conversion
# COLUMNS = ['Cells', 'Dir', 'User', 'Segments', 'Multiplier', 'Cosine',
# 'Energy', 'Time', 'Cor C', 'Cor B', 'Cor A', 'Value', 'Error']
CONV = {'Result': 'Value', 'Rel': 'Error', 'R': 'Cor A', 'Z': 'Cor B',
'Th': 'Cor C'}
class Meshtal:
def __init__(self, filepath):
"""
Object representing the meshtal MCNP file.
Parameters
----------
filepath : path or str
path to the meshtal MCNP file.
Returns
-------
None.
"""
self.filepath = filepath # file path
self.name = os.path.basename(filepath) # file name
self.fmeshes = self._read_file() # dictionary of the fmeshes
def extract_1D(self):
"""
Iterate on the fmeshes and select the one that can be compressed into
a 1D tally and convert them.
Returns
-------
fmesh1D : dic
dictionary containing the converted 1D fmeshes
"""
fmesh1D = {}
for key, fmesh in self.fmeshes.items():
flag1D, ax = fmesh.is1D()
# If the fmesh can compressed to a 1D
if flag1D:
# Extract tally data
_, data = fmesh.convert2tally()
# keepcols = [ax, fmesh._values_tag, fmesh._error_tag]
# print(data)
# data = fmesh.data[keepcols]
fmesh1D[key] = {'data': data, 'num': key,
'desc': fmesh.description}
self.fmesh1D = fmesh1D
return fmesh1D
def _read_file(self):
"""
read the MCNP meshtal file and populate the Meshtal object.
Returns
-------
fmeshes : TYPE
DESCRIPTION.
"""
with open(self.filepath, 'r') as infile:
# Flags that regulates current operations
flag_inheader = True
flag_intally = False
flag_inmesh = False
# default values
particle = None
description = None
fmeshes = {}
for idx, line in enumerate(infile):
# --- Operations while reading the file header ---
if flag_inheader:
# Things to look for
tally_num = PAT_NUM.search(line)
# Finding a tally num triggers the exit from header
if tally_num is not None:
flag_inheader = False
flag_intally = True
# New Fmesh initialized
current_num = tally_num.group().strip()
# --- Operations while reading the tally header ---
elif flag_intally:
# Things to look for
particle_check = PAT_PARTICLE.search(line)
description_check = PAT_DESC.search(line)
cyl_start = PAT_CYLYNDER.search(line)
if description_check is not None:
description = description_check.group().strip()
if particle_check is not None:
particle = particle_check.group().strip()
# Finding a cylindrical fmesh start triggers the entering
# in the mesh mode
if cyl_start is not None:
flag_intally = False
flag_inmesh = True
skiprows = idx # Memorize where reading should start
nrows = 1
# --- Operations while reading the tally values ---
elif flag_inmesh:
# Just check when the data finishes (blank line)
if len(line.strip()) == 0:
flag_inheader = True
flag_inmesh = False
# Blank line, read and adjourn fmesh
fmesh_data = pd.read_csv(self.filepath,
sep=r'\s+',
skiprows=skiprows,
nrows=nrows-1)
# Generate the FMESH and update the dic
fmesh = Fmesh(fmesh_data, current_num, description,
particle)
fmeshes[current_num] = fmesh
# Reistantiate default values
particle = None
description = None
else:
# one more line to read
nrows += 1
# --- At the end of file some more operation may be needed ---
# If we were still reading tally add it
if flag_inmesh:
fmesh_data = pd.read_csv(self.filepath, sep=r'\s+',
skiprows=skiprows,
nrows=nrows)
# Generate the FMESH and update the dic
fmesh = Fmesh(fmesh_data, current_num, description, particle)
fmeshes[current_num] = fmesh
return fmeshes
class Fmesh:
def __init__(self, data, tallynum, description, particle):
"""
Special Fmesh tally object representation
Parameters
----------
data : pd.DataFrame
tally result data.
tallynum : str
tally MCNP number.
description : str
description of the tally.
particle : str
tallied particle.
Returns
-------
None.
"""
self.data = data.dropna(axis=1)
self.tallynum = tallynum
self.description = description
self.particle = particle
self._values_tag = 'Result'
self._error_tag = 'Rel'
def is1D(self):
"""
This method checks if an fmesh tally can be compressed into 1D
tally.
Returns
-------
flag_1D : Bool
If True, the mesh tally has only one true ax.
ax : str
name of the true ax. If the fmesh has more than one axis None is
returned.
"""
df = self.data.copy()
axes = []
for column in df.columns:
# Iterate on all columns except the results and errors
if column not in [self._values_tag, self._error_tag]:
check = set(df[column].values)
# If the column only has a single costant value it is not a
# true ax
if len(check) > 1:
axes.append(check)
ax = str(column)
# Check if there was only one ax
if len(axes) == 1:
flag_1D = True
else:
flag_1D = False
ax = None
return flag_1D, ax
def convert2tally(self):
"""
Access the fmesh results and get the data columns compatible with the
mctal classic tallies ones
Parameters
----------
Returns
-------
str
tally number.
data : pd.DataFrame
data with compatible columns name.
"""
# First of all check if the tally is 1D, in that case reduce the data
flag1D, ax = self.is1D()
data = self.data.copy()
newcols = []
for column in data.columns:
if flag1D:
# Add to the new data only the necessary if is 1D
if column in [ax, self._values_tag, self._error_tag]:
try:
newcols.append(CONV[column])
except KeyError:
print('Key: "'+column+'" is not yet convertible')
else:
# If it not to keep just drop the column
del data[column]
# If it is not a 1D just convert the columns names
else:
try:
newcols.append(CONV[column])
except KeyError:
print('Key: "'+column+'" is not yet convertible')
data.columns = newcols
return self.tallynum, data