summaryrefslogtreecommitdiff
path: root/simdata.py
blob: 77fb5a898409e9b142fbfd4fb1b8d66e43e1816d (plain)
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
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
"""
This module provides a wrapper around a Cactus simulation output, i.e. a directory of HDF5 files.
"""

import os
from collections.abc import MutableMapping
from functools import cached_property

import numpy as np

from math_utils.array_utils import array_reflect

from . import datafile


class _RefinedSlice(object):
    # simulation time on this slice
    time = -1
    # simulation iteration on this slice
    it   = -1

    # a list of arrays of coordinate points along each direction [x, y, z]
    c = None

    # arrays of coordinate points along each direction
    x = None
    y = None
    z = None

    rl = None

    # private
    _sd = None

    def __init__(self, sd, rl, time = -1, iteration = -1):
        self._sd = sd
        self.rl  = rl

        if time < 0 and iteration < 0:
            raise TypeError('Neither time nor iteration provided')

        trial_df = sd._df

        if time >= 0:
            self.it = trial_df._iter_from_time(time)
        else:
            self.it = iteration

        try:
            s = trial_df.slice(it = self.it, rl = rl.n)
            self.time = s.time
        except KeyError:
            if time < 0:
                raise IndexError('No such iteration: ' + str(iteration))
            else:
                raise IndexError('No such time: ' + str(time))

        ds = trial_df.slice(self.it, rl = rl.n)
        self.c = ds.layout.coords
        self.x, self.y, self.z = self.c

    def __getitem__(self, key):
        if key in self._sd.df:
            return self._sd.df[key].slice(self.it, rl = self.rl.n)
        raise IndexError

class _AxisData:
    """
    Wrapper providing convenient access to quantities along a given axis
    as functions of time. Data is exported as (t, x) 2D arrays.


    """

    sd        = None
    "parent simulation data"

    axis      = None
    "axis along which the data is loaded, one of: x/y/z"
    axis_idx  = None
    "axis index: 0/1/2 for x/y/z"

    rl        = None
    "refinement level number"

    it        = None
    "1D array of simulation iterations on which data is available"

    t         = None
    "1D array of coordinate times, same shape as it"

    x         = None
    "1D array of spatial coordinates on the axis"

    T         = None
    "2D array of times corresponding to returned data"

    X         = None
    "2D array of spatial coordinates corresponding to returned data"

    _reflect    = None
    _zero_shift = None
    _data_sl    = None

    def __init__(self, sd, rl, *,
                 axis = 'x', maxtime = None, reflect = False,
                 zero_shift = None):
        self.sd = sd
        self.rl = sd.rl[rl].n

        if axis == 'x':
            self.axis_idx = 0
        elif axis == 'y':
            self.axis_idx = 1
        elif axis == 'z':
            self.axis_idx = 2
        else:
            raise ValueError('axis must be one of: x y z')
        self.axis      = axis

        self._reflect    = reflect
        self._zero_shift = zero_shift

        df_name = os.path.splitext(os.path.basename(sd._df.path))[0].split('.')[0]
        df      = sd.df['%s.%s' % (df_name, axis)]

        itertimes  = df.itertimes[rl]
        if maxtime is not None:
            itertimes = list(filter(lambda it: it[1] <= maxtime, itertimes))
        if len(itertimes) < 1:
            raise ValueError('No data for maxtime: %s' % maxtime)

        self.it, self.t = map(np.array, zip(*itertimes))

        s      = df.slice(it = self.it[0], rl = self.rl)
        x      = s.layout.coords[0]

        ghosts        = np.nonzero(np.abs(x) < 1e-10)[0][0]
        self._data_sl = slice(ghosts, -ghosts)

        x = x[self._data_sl]
        if reflect:
            x = array_reflect(x, parity = -1.0)

        self.x = x

        self.T, self.X = np.meshgrid(self.t, self.x, indexing = 'ij')

    def _load_data(self, gf, parity):
        df = self.sd.df['%s.%s' % (gf, self.axis)]

        ret = []
        for it in self.it:
            d = df.slice(it, rl = self.rl).data[self._data_sl]
            if self._reflect:
                d = array_reflect(d, parity = parity)

            ret.append(d)
        return np.array(ret)

    # scalars
    @cached_property
    def alpha(self):    return self._load_data('alpha', 1.0)
    @cached_property
    def W(self):        return self._load_data('W', 1.0)
    @cached_property
    def phi(self):      return self._load_data('phi', 1.0)
    @cached_property
    def trK(self):      return self._load_data('trK', 1.0)
    @cached_property
    def zeta(self):     return self._load_data('zeta', 1.0)
    @cached_property
    def Kretsch(self):  return self._load_data('Kretsch', 1.0)
    @cached_property
    def H(self):        return self._load_data('H', 1.0)

    # vectors
    @cached_property
    def betax(self):
        if self._zero_shift:
            return np.zeros_like(self.T)
        return self._load_data('beta1', -1.0 if self.axis == 'x' else 1.0)
    @cached_property
    def betay(self):
        if self._zero_shift:
            return np.zeros_like(self.T)
        return self._load_data('beta2', -1.0 if self.axis == 'y' else 1.0)
    @cached_property
    def betaz(self):
        if self._zero_shift:
            return np.zeros_like(self.T)
        return self._load_data('beta3', -1.0 if self.axis == 'z' else 1.0)

    @cached_property
    def conf_fact(self):      return 1.0 / (self.phi * self.phi)

    # 2-tensors
    @cached_property
    def gtxx(self):   return self._load_data('gt11', 1.0)
    @cached_property
    def gtyy(self):   return self._load_data('gt22', 1.0)
    @cached_property
    def gtzz(self):   return self._load_data('gt33', 1.0)
    @cached_property
    def Atxx(self):   return self._load_data('At11', 1.0)
    @cached_property
    def Atyy(self):   return self._load_data('At22', 1.0)
    @cached_property
    def Atzz(self):   return self._load_data('At33', 1.0)

    @cached_property
    def gtxy(self):
        return self._load_data('gt12', 1.0 if self.axis == 'z' else -1.0)
    @cached_property
    def gtxz(self):
        return self._load_data('gt13', 1.0 if self.axis == 'y' else -1.0)
    @cached_property
    def gtyz(self):
        return self._load_data('gt23', 1.0 if self.axis == 'x' else -1.0)
    @cached_property
    def Atxy(self):
        return self._load_data('At12', 1.0 if self.axis == 'z' else -1.0)
    @cached_property
    def Atxz(self):
        return self._load_data('At13', 1.0 if self.axis == 'y' else -1.0)
    @cached_property
    def Atyz(self):
        return self._load_data('At23', 1.0 if self.axis == 'x' else -1.0)

    @cached_property
    def gxx(self):   return self.gtxx * self.conf_fact
    @cached_property
    def gyy(self):   return self.gtyy * self.conf_fact
    @cached_property
    def gzz(self):   return self.gtzz * self.conf_fact
    @cached_property
    def Kxx(self):   return self.Atxx * self.conf_fact + self.gxx * self.trK / 3.
    @cached_property
    def Kyy(self):   return self.Atyy * self.conf_fact + self.gyy * self.trK / 3.
    @cached_property
    def Kzz(self):   return self.Atzz * self.conf_fact + self.gzz * self.trK / 3.

    @cached_property
    def gxy(self):   return self.gtxy * self.conf_fact
    @cached_property
    def gxz(self):   return self.gtxz * self.conf_fact
    @cached_property
    def gyz(self):   return self.gtyz * self.conf_fact
    @cached_property
    def Kxy(self):   return self.Atxy * self.conf_fact + self.gxy * self.trK / 3.
    @cached_property
    def Kxz(self):   return self.Atxz * self.conf_fact + self.gxz * self.trK / 3.
    @cached_property
    def Kyz(self):   return self.Atyz * self.conf_fact + self.gyz * self.trK / 3.

    @cached_property
    def gamma(self):
        """
        spatial metric
        """
        return np.array([[self.gxx, self.gxy, self.gxz],
                         [self.gxz, self.gyy, self.gyz],
                         [self.gxz, self.gyz, self.gzz]])

    @cached_property
    def K(self):
        """
        spatial extrinsic curvature
        """
        ret = np.array([[self.Axx, self.Axy, self.Axz],
                        [self.Axz, self.Ayy, self.Ayz],
                        [self.Axz, self.Ayz, self.Azz]])

class _RefinementLevel(object):
    # refinement level number
    n  = -1

    # private
    _sd = None

    def __init__(self, sd, n):
        self._sd = sd
        self.n   = n

    def slice(self, iteration = -1, time = -1):
        return _RefinedSlice(self._sd, self, time, iteration)

    @property
    def itertimes(self):
        return self._sd._df.itertimes[self.n]

class _DataDir(MutableMapping):
    _files = None
    _paths = None

    def __init__(self):
        self._files = {}
        self._paths = {}

    def __setitem__(self, key, val):
        if key in self._files:
            raise ValueError

        self._files[key] = None
        self._paths[key] = val

    def __delitem__(self, key):
        if self._files[key]:
            self._files[key].close()
        del self._files[key]
        del self._paths[key]

    def __getitem__(self, key):
        if not key in self._paths:
            raise KeyError(key)
        elif self._files[key] is None:
            self._files[key] = datafile.DataFile(self._paths[key])

        return self._files[key]

    def __len__(self):
        return len(self._paths)

    def __iter__(self):
        for it in self._paths:
            yield it

    def close(self):
        for f in self._files.values():
            if f is not None:
                f.close()
        self._files.clear()
        self._paths.clear()

class SimulationData(object):
    "The full path to the simulation directory"
    dirname = None

    "A dictionary of { basename : DataFile } items representing the data files. The basename is the file name without the .h5 extension"
    df = None

    # per-refinement level parameters
    rl = None

    _df = None

    def __init__(self, dirname):
        self.dirname = os.path.abspath(dirname)
        self.df = _DataDir()

        # open all the hdf5 files in the dir
        for f in os.listdir(self.dirname):
            if (not f.endswith('.h5') or f.startswith('checkpoint') or f.endswith('.d.h5')):
                continue

            self.df[f[:-3]] = '%s/%s' % (self.dirname, f)
        if len(self.df) == 0:
            raise ValueError('No HDF5 data files in the directory.')

        # pick a representative datafile and get the grid properties from it
        if 'H' in self.df:
            df = self.df['H']
        else:
            df = next(iter(self.df.values()))

        self._df = df

        # get the refinement levels, iterations and times
        self.rl = []
        for rl in df.rl:
            self.rl.append(_RefinementLevel(self, len(self.rl)))

    def axis_data(self, rl, **kwargs):
        return _AxisData(self, rl, **kwargs)

    def close(self):
        self.df.close()