def __init__(self, dist, data, l_min=0): """Constructor. :param dist: Radius of the spherical surface. :type dist: float :param data: List of tuples with the two multipolar numbers and the data as :py:class:`~.TimeSeries`. :type data: list of tuple ``(l, m, timeseries)`` :ivar l_min: l smaller than ``l_min`` are dropped. :type l_min: int """ self.dist = float(dist) self.radius = self.dist # This is just an alias self.radius_series = None self.l_min = l_min # Associate multipoles to list of timeseries multipoles_list_ts = {} # Now we populate the multipoles_ts_list dictionary. This is a # dictionary with keys (l, m) and with values lists of timeseries. If # the key is not already present, we create it with value an empty # list, then we append the timeseries to this list for mult_l, mult_m, ts in data: # mult = "multipole" if mult_l >= l_min: lm_list = multipoles_list_ts.setdefault((mult_l, mult_m), []) # This means: multipoles[(mult_t, mult_m)] = [] lm_list.append(ts) # At the end we have: # multipoles[(mult_t, mult_m)] = [ts1, ts2, ...] # Now self._multipoles is a dictionary in which all the timeseries are # collapse in a single one. So it is a straightforward map (l, m) -> ts self._multipoles = { lm: timeseries.combine_ts(ts) for lm, ts in multipoles_list_ts.items() } self.available_l = sorted( {mult_l for mult_l, _ in self._multipoles.keys()}) self.l_max = max(self.available_l) self.available_m = sorted( {mult_m for _, mult_m in self._multipoles.keys()}) self.available_lm = set(self._multipoles.keys()) # Check if all the (l, m) from l_min to l_max are available all_lm = set() for mult_l in range(self.l_min, max(self.available_l) + 1): for mult_m in range(-mult_l, mult_l + 1): all_lm.add((mult_l, mult_m)) # set subtraction self.missing_lm = all_lm - self.available_lm # Data is in the format expected by __init__ self.data = [(lm[0], lm[1], ts) for lm, ts in self._multipoles.items()]
def test_combine_ts(self): times1 = np.linspace(0, 2 * np.pi, 100) sins1 = np.sin(times1) times2 = np.linspace(np.pi, 3 * np.pi, 100) coss1 = np.cos(times2) # A sine wave + half a cos wave expected_early = np.append(sins1, np.cos(times2[50:])) expected_late = np.append(sins1[:50], np.cos(times2)) ts1 = ts.TimeSeries(times1, sins1) ts2 = ts.TimeSeries(times2, coss1) self.assertTrue( np.allclose( ts.combine_ts([ts1, ts2], prefer_late=False).y, expected_early ) ) self.assertTrue( np.allclose(ts.combine_ts([ts1, ts2]).y, expected_late) ) # Here we test two timeseries with same tmin times4 = np.linspace(0, 2 * np.pi, 100) times5 = np.linspace(0, 3 * np.pi, 100) sins4 = np.sin(times4) coss5 = np.sin(times5) ts4 = ts.TimeSeries(times4, sins4) ts5 = ts.TimeSeries(times5, coss5) self.assertTrue( np.allclose( ts.combine_ts([ts1, ts2], prefer_late=False).y, expected_early ) ) self.assertTrue( np.allclose(ts.combine_ts([ts4, ts5], prefer_late=True).y, coss5) )
def __getitem__(self, key): if key not in self: raise KeyError(f"{key} not available") if key not in self._vars: # We read all the files associated to variable key folders = self._vars_readers[key] series = [f[key] for f in folders.values()] self._vars[key] = ts.combine_ts(series) return self._vars[key]
def _populate_ah_vars(self, sd): # First, we find all the files related to apparent horizons. These # have names like BH_diagnostics.ah1.gp self._ah_files = {} rx_ah_filename = re.compile(r"^BH_diagnostics.ah(\d+).gp$") for path in sd.allfiles: filename = os.path.split(path)[-1] matched = rx_ah_filename.search(filename) if matched is not None: ah_index = int(matched.group(1)) self._ah_files.setdefault(ah_index, []).append(path) # Next, we find what variables they contain. This should be pretty # standard, but we can make our code more robust by not assuming too # much. We read one header and find the variables, then read all the # other files assuming the have the same variables. A complication is # that variables names have blank spaces. We turn them into underscores. self._num_ah_horizons = len(self._ah_files.keys()) # We continue only if we find some files if self._num_ah_horizons > 0: first_ah_file = next(iter(self._ah_files.values()))[0] with open(first_ah_file, "r") as fil: # Here we read the first lines_to_read into header # We strip the new line header = [] for line in fil: # We read the header, which starts with # if line.startswith("#"): header.append(line.strip()) else: break # Now, we parse the header and associate variable name with column # where the data is. The header looks like: # # # apparent horizon 1/3 # # # # column 1 = cctk_iteration # # column 2 = cctk_time # # column 3 = centroid_x # # column 4 = centroid_y # # column 5 = centroid_z # # We scan the columns with a regex. # 1. ^ $ means that we match the entire string # 2. \#[\s]column[\s]+ matches the literal '# column ' with any # number of spaces. # 3. Then we match the number # 4. We match another literal ' = ' with the sapces # 5. Finally we match the name of the variable matching letters # and symbols rx_column = re.compile( r"\#[\s]column[\s]+(\d+)[\s]=[\s]([a-zA-Z_0-9\s()-/]+)$") # Here is where we store the map self._ah_vars_columns = {} for line in header: matched = rx_column.match(line) if matched is not None: # Columns counting start from 1, so we must subtract one to # be with 0-based indexing column_number = int(matched.group(1)) - 1 name = matched.group(2) # We need to know where the time is if name == "cctk_time": time_column = column_number # We exclude some variables we don't want in OneHorizon # (e.g., we don't want cctk_time) if name in self._exclude_ah_vars: continue # Spaces to underscores name = name.replace(" ", "_") # We remove parentheses name = name.replace("(", "") name = name.replace(")", "") # We change / to - name = name.replace("/", "-") self._ah_vars_columns[name] = column_number # Now we are ready to populate, we read all the data first. Then, we # select all the columns for ah_index, files in self._ah_files.items(): # We create an empty dictionary in self._ah_vars[ah_index] self._ah_vars.setdefault(ah_index, {}) # We read all the data alldata = [np.loadtxt(f, unpack=True, ndmin=2) for f in files] for var_name, column_number in self._ah_vars_columns.items(): # Here we select the time column and the data column for all # the data in each file and we convert them into TimeSeries data_ts = combine_ts([ TimeSeries(data[time_column], data[column_number]) for data in alldata ]) self._ah_vars[ah_index][var_name] = data_ts
def __getitem__(self, key): # We read all the files associated to variable key folders = self._vars[key] series = [f.load(key) for f in folders.values()] return ts.combine_ts(series)
def test_MultipoleOneDet(self): ts_comb = ts.combine_ts([self.ts1, self.ts2]) data = [(2, 2, self.ts1), (2, 2, self.ts2)] data2 = [(2, 2, self.ts1), (2, -2, self.ts2)] data3 = [(2, 2, self.ts1), (1, 1, self.ts2)] # Combinging ts mult1 = mp.MultipoleOneDet(100, data) # Different multipoles mult2 = mp.MultipoleOneDet(100, data2) # l_min != 0 mult3 = mp.MultipoleOneDet(100, data3, l_min=2) self.assertEqual(mult1.dist, 100) self.assertEqual(mult1.radius, 100) self.assertEqual(mult1.l_min, 0) self.assertEqual(mult3.l_min, 2) # test __call__ self.assertEqual(mult1(2, 2), ts_comb) self.assertEqual(mult2(2, 2), self.ts1) self.assertEqual(mult2(2, -2), self.ts2) # test copy() self.assertEqual(mult1.copy(), mult1) self.assertIsNot(mult1.copy(), mult1) # test available_ self.assertCountEqual(mult1.available_l, {2}) self.assertCountEqual(mult2.available_l, {2}) self.assertCountEqual(mult1.available_m, {2}) self.assertCountEqual(mult2.available_m, {2, -2}) self.assertCountEqual(mult1.available_lm, {(2, 2)}) self.assertCountEqual(mult2.available_lm, {(2, 2), (2, -2)}) self.assertCountEqual(mult3.available_l, {2}) self.assertCountEqual(mult3.available_m, {2}) self.assertCountEqual(mult3.missing_lm, {(2, -2), (2, -1), (2, 0), (2, 1)}) # test contains self.assertIn((2, 2), mult1) self.assertIn((2, -2), mult2) self.assertEqual(mult2[(2, 2)], self.ts1) # test_iter # Notice the order. It is increasing in (l, m) expected = [(2, -2, self.ts2), (2, 2, self.ts1)] for data, exp in zip(mult2, expected): self.assertCountEqual(data, exp) # test __len__ self.assertEqual(len(mult1), 1) self.assertEqual(len(mult2), 2) # test keys() self.assertCountEqual(mult1.keys(), [(2, 2)]) # test __eq__() self.assertNotEqual(mult1, mult2) self.assertNotEqual(mult1, 1) self.assertEqual(mult1, mult1) # test __str__() self.assertIn("(2, 2)", mult1.__str__()) self.assertIn("missing", mult3.__str__())