def test_integrate(self):

        times_long = np.linspace(0, 2 * np.pi, 10000)
        values_long = np.sin(times_long)
        TS = ts.TimeSeries(times_long, values_long)
        TS_c = ts.TimeSeries(times_long, values_long + 1j * values_long)

        self.assertTrue(
            np.allclose(TS.integrated().y, 1 - np.cos(times_long), atol=1e-4))

        self.assertTrue(
            np.allclose(
                TS_c.integrated().y,
                1 - np.cos(times_long) + 1j * (1 - np.cos(times_long)),
                atol=1e-4,
            ))

        sins = TS.copy()
        sins.integrate()

        self.assertTrue(np.allclose(sins.y, 1 - np.cos(times_long), atol=1e-4))

        # Masked data
        with self.assertRaises(RuntimeError):
            self.TS_cm.integrated()
    def test_save(self):

        times = np.logspace(0, 1, 10)
        sins = ts.TimeSeries(times, np.sin(times))
        compl = ts.TimeSeries(times, np.sin(times) + 1j * np.sin(times))

        sins_file = "test_save_sins.dat"
        compl_file = "test_save_compl.dat"

        sins.save(sins_file)
        compl.save(compl_file)

        loaded_sins = np.loadtxt(sins_file).T
        os.remove(sins_file)

        loaded_compl = np.loadtxt(compl_file).T
        os.remove(compl_file)

        self.assertTrue(np.allclose(loaded_sins[0], times))
        self.assertTrue(np.allclose(loaded_sins[1], np.sin(times)))

        self.assertTrue(np.allclose(loaded_compl[0], times))
        self.assertTrue(np.allclose(loaded_compl[1], np.sin(times)))
        self.assertTrue(np.allclose(loaded_compl[2], np.sin(times)))

        with self.assertWarns(RuntimeWarning):
            path = "/tmp/tmp_kuibit_tmp.dat"
            self.TS_cm.save("/tmp/tmp_kuibit_tmp.dat")
            os.remove(path)
    def test_time_unit_change(self):

        sins = ts.TimeSeries(self.times, self.values)

        new_times = np.linspace(0, np.pi, 100)
        new_times_inverse = np.linspace(0, 6 * np.pi, 100)

        self.assertTrue(np.allclose(sins.time_unit_changed(2).t, new_times))

        self.assertTrue(
            np.allclose(
                sins.time_unit_changed(3, inverse=True).t, new_times_inverse
            )
        )

        sins.time_unit_change(2)

        self.assertTrue(np.allclose(sins.t, new_times))

        sins.time_unit_change(6, inverse=True)

        self.assertTrue(np.allclose(sins.t, new_times_inverse))

        two_times = np.linspace(0, 4 * np.pi, 100)

        sins = self.TS.copy()

        sins_half_frequency = ts.TimeSeries(two_times, np.sin(0.5 * two_times))

        self.assertEqual(sins.redshifted(1), sins_half_frequency)

        sins.redshift(1)

        self.assertEqual(sins, sins_half_frequency)
    def setUp(self):
        self.times = np.linspace(0, 2 * np.pi, 100)
        self.values = np.sin(self.times)

        self.TS = ts.TimeSeries(self.times, self.values)
        # Complex
        self.TS_c = ts.TimeSeries(self.times, self.values + 1j * self.values)
    def test_init(self):
        # Check that errors are thrown if:
        # 1. There is a mismatch between t and y
        # 2. The timeseries is empty

        # 1
        times = np.linspace(0, 2 * np.pi, 100)
        values = np.array([1, 2, 3])

        with self.assertRaises(ValueError):
            ts.TimeSeries(times, values)

        # 2
        times = np.array([])
        values = np.array([])

        with self.assertRaises(ValueError):
            ts.TimeSeries(times, values)

        # Test timeseries with one element
        scalar = ts.TimeSeries(0, 0)

        self.assertEqual(scalar.y, 0)

        # Test guarantee_x_is_monotonic
        # This should not throw an error even if it is wrong
        times = np.linspace(2 * np.pi, 0, 100)
        values = np.sin(times)
        wrong = ts.TimeSeries(times, values, guarantee_t_is_monotonic=True)
        self.assertCountEqual(wrong.t, times)
    def test__apply_binary(self):
        # Check that errors are thrown if:
        # 1. Lists have different times
        # 2. The object is unknown

        # We as example of a function np.sum

        # 1a
        times = np.linspace(0, 2 * np.pi, 200)
        values = np.cos(times)
        cos = ts.TimeSeries(times, values)

        with self.assertRaises(ValueError):
            out = self.TS._apply_binary(cos, np.add)

        # 1b
        times = np.linspace(2, 3 * np.pi, 100)
        values = np.cos(times)
        cos = ts.TimeSeries(times, values)

        with self.assertRaises(ValueError):
            out = self.TS._apply_binary(cos, np.add)

        # 2
        with self.assertRaises(TypeError):
            self.TS._apply_binary("str", np.add)
    def test_differentiate(self):

        times = np.linspace(0, 2 * np.pi, 1000)
        values = np.sin(times)

        higher_res_TS = ts.TimeSeries(times, values)
        higher_res_TS_c = ts.TimeSeries(times, values + 1j * values)

        self.assertTrue(
            np.allclose(higher_res_TS.differentiated().y,
                        np.cos(times),
                        atol=1e-3))

        self.assertTrue(
            np.allclose(higher_res_TS.differentiated(2).y,
                        -np.sin(times),
                        atol=5e-2))

        self.assertTrue(
            np.allclose(
                higher_res_TS_c.differentiated().y,
                np.cos(times) + 1j * np.cos(times),
                atol=1e-3,
            ))

        sins = higher_res_TS.copy()
        sins.differentiate()

        self.assertTrue(np.allclose(sins.y, np.cos(times), atol=1e-3))

        sins = higher_res_TS.copy()

        with self.assertRaises(ValueError):
            sins.spline_differentiated(8)

        # The boundaries are not accurate
        self.assertTrue(
            np.allclose(
                higher_res_TS.spline_differentiated().y,
                np.cos(times),
                atol=1e-3,
            ))

        self.assertTrue(
            np.allclose(
                higher_res_TS.spline_differentiated(2).y,
                -np.sin(times),
                atol=1e-3,
            ))

        self.assertTrue(
            np.allclose(
                higher_res_TS_c.spline_differentiated().y,
                np.cos(times) + 1j * np.cos(times),
                atol=1e-3,
            ))

        sins.spline_differentiate()

        self.assertTrue(np.allclose(sins.y, np.cos(times), atol=1e-3))
Exemple #8
0
    def setUp(self):
        self.num_times = 4000
        self.times = np.linspace(0, 20 * 2 * np.pi, self.num_times)
        self.values1 = np.sin(30 * self.times)
        self.values2 = np.sin(60 * self.times) * np.cos(30 * self.times)

        self.ts1 = ts.TimeSeries(self.times, self.values1)
        self.ts2 = ts.TimeSeries(self.times, self.values2)
    def setUp(self):

        # Prepare fake multipoles
        self.t1 = np.linspace(0, 1, 100)
        self.t2 = np.linspace(2, 3, 100)
        self.y1 = np.sin(self.t1)
        self.y2 = np.sin(self.t2)
        self.ts1 = ts.TimeSeries(self.t1, self.y1)
        self.ts2 = ts.TimeSeries(self.t2, self.y2)
Exemple #10
0
    def test_make_spline_call(self):

        # Cannot make a spline with 1 point
        with self.assertRaises(ValueError):
            sins = ts.TimeSeries([0], [0])
            sins._make_spline()

        # Check that spline reproduce data
        # These are pulled from the data
        self.assertTrue(np.allclose(self.TS(self.times), self.values))
        # These have some that computed with splines
        other_times = np.linspace(0, np.pi, 100)
        other_values = np.sin(other_times)

        self.assertTrue(np.allclose(self.TS(other_times), other_values))

        self.assertTrue(np.allclose(self.TS(np.pi / 2), 1))

        # Vector input in, vector input out
        self.assertTrue(isinstance(self.TS([np.pi / 2]), np.ndarray))

        self.assertTrue(np.allclose(self.TS(self.TS.t[0]), self.TS.y[0]))

        # From data
        self.assertTrue(
            np.allclose(self.TS_c(self.times), self.values + 1j * self.values))

        # From spline
        self.assertTrue(
            np.allclose(self.TS_c(other_times),
                        other_values + 1j * other_values))

        # Masked data
        with self.assertRaises(RuntimeError):
            self.TS_cm(other_times)

        # Does the spline update?
        # Let's test with a method that changes the timeseries
        sins = ts.TimeSeries(self.times, self.values + 1)
        self.assertTrue(np.allclose(sins(self.times), self.values + 1))

        sins.mean_remove()

        self.assertTrue(np.allclose(sins(self.times), self.values))

        with self.assertRaises(ValueError):
            sins(-1)

        # Test that setting directly the members invalidates the spline
        sins.y *= 2
        self.assertTrue(sins.invalid_spline)
        sins.invalid_spline = False
        sins.t *= 2
        self.assertTrue(sins.invalid_spline)
Exemple #11
0
    def setUp(self):
        self.times = np.linspace(0, 2 * np.pi, 100)
        self.values = np.sin(self.times)

        self.TS = ts.TimeSeries(self.times, self.values)
        # Complex
        self.TS_c = ts.TimeSeries(self.times, self.values + 1j * self.values)

        # Complex and masked
        self.vals_cm = np.ma.log10(self.values + 1j * self.values)
        self.TS_cm = ts.TimeSeries(self.times, self.vals_cm)
    def test_MultipolesDir(self):

        sim = sd.SimDir("tests/tov")
        cacdir = mp.MultipolesDir(sim)

        # multipoles from textfile
        with self.assertRaises(RuntimeError):
            cacdir._multipole_from_textfile(
                "tests/tov/output-0000/static_tov/carpet-timing..asc")

        path = "tests/tov/output-0000/static_tov/mp_Phi2_l2_m-1_r110.69.asc"
        path_h5 = "tests/tov/output-0000/static_tov/mp_harmonic.h5"
        t, real, imag = np.loadtxt(path).T

        with h5py.File(path_h5, "r") as data:
            # Loop over the groups in the hdf5
            a = data["l2_m2_r8.00"][()].T

        mpts = ts.TimeSeries(t, real + 1j * imag)
        ts_h5 = ts.TimeSeries(a[0], a[1] + 1j * a[2])

        self.assertEqual(mpts, cacdir._multipole_from_textfile(path))
        self.assertEqual(
            ts_h5,
            cacdir._multipoles_from_h5files([path_h5])[8.00](2, 2),
        )

        mpfiles = [(2, 2, 100, path)]

        # Check one specific case
        self.assertEqual(
            mpts,
            cacdir._multipoles_from_textfiles(mpfiles)[100](2, 2),
        )

        self.assertEqual(cacdir["phi2"][110.69](2, -1), mpts)
        self.assertEqual(cacdir["harmonic"][8.00](2, 2), ts_h5)

        # test get
        self.assertIs(cacdir.get("bubu"), None)
        self.assertEqual(cacdir.get("harmonic")[8.00](2, 2), ts_h5)

        # test __getitem__
        with self.assertRaises(KeyError):
            cacdir["bubu"]

        # test __contains__
        self.assertIn("phi2", cacdir)

        # test keys()
        self.assertCountEqual(cacdir.keys(), ["harmonic", "phi2", "psi4"])

        # test __str__()
        self.assertIn("harmonic", cacdir.__str__())
    def test_is_regularly_sampled(self):
        self.assertTrue(self.TS.is_regularly_sampled())

        # Log space is not regular
        times = np.logspace(0, 1, 100)
        ts_log = ts.TimeSeries(times, self.values)
        self.assertFalse(ts_log.is_regularly_sampled())

        # If the series is only one point long, an error should be raised
        with self.assertRaises(RuntimeError):
            ts.TimeSeries([1], [1]).is_regularly_sampled()
    def test_div(self):
        # Errors are tested by test_apply_binary

        with self.assertRaises(ValueError):
            out = self.TS / 0

        # Let's avoid division by 0

        times = np.linspace(np.pi / 3, np.pi / 2, 100)
        values = np.sin(times)

        sins = ts.TimeSeries(times, values)

        out = sins / sins
        self.assertTrue(np.allclose(out.y, 1))

        # Test rtrudiv
        out = 1 / sins
        self.assertTrue(np.allclose(out.y, 1 / values))

        # Scalar
        out = sins / 3
        self.assertTrue(np.allclose(out.y, np.sin(times) / 3))

        # Test itruediv
        out /= 2
        self.assertTrue(np.allclose(out.y, np.sin(times) / 6))

        out /= sins
        self.assertTrue(np.allclose(out.y, 1 / 6))
Exemple #15
0
 def test_ts(name, *args):
     np_func = getattr(np.ma, f"masked_{name}")
     # We will edit in place t
     t = self.TS.copy()
     getattr(t, f"mask_{name}")(*args)
     self.assertTrue(
         t, ts.TimeSeries(self.TS.x, np_func(self.TS.y, *args)))
Exemple #16
0
    def setUp(self):
        # Let's test with a TimeSeries, a UniformGridData, and a
        # HierarchicalGridData

        x = np.linspace(0, 2 * np.pi, 100)
        y = np.sin(x)

        self.TS = ts.TimeSeries(x, y)

        self.grid_2d = gd.UniformGrid([10, 20], x0=[0.5, 1], dx=[1, 1])

        self.ugd = gdu.sample_function_from_uniformgrid(
            lambda x, y: x * (y + 2), self.grid_2d)

        grid_2d_1 = gd.UniformGrid([10, 20],
                                   x0=[0.5, 1],
                                   dx=[1, 1],
                                   ref_level=0)

        self.ugd1 = gdu.sample_function_from_uniformgrid(
            lambda x, y: x * (y + 2), grid_2d_1)

        grid_2d_2 = gd.UniformGrid([10, 20],
                                   x0=[1, 2],
                                   dx=[3, 0.4],
                                   ref_level=1)

        self.ugd2 = gdu.sample_function_from_uniformgrid(
            lambda x, y: x * (y + 2), grid_2d_2)

        self.hg = gd.HierarchicalGridData([self.ugd1, self.ugd2])
    def _multipoles_from_h5file(path):
        """Read multipole data from a HDF5 file.

        :param path: File to read.
        :type path: str
        :returns: Multipole data.
        :rtype: :py:class:`~.TimeSeries`
        """
        alldets = []
        # This regex matches : l(number)_m(-number)_r(number)
        fieldname_pattern = re.compile(r"l(\d+)_m([-]?\d+)_r([0-9.]+)")

        with h5py.File(path, "r") as data:

            # Loop over the groups in the hdf5
            for entry in data.keys():
                matched = fieldname_pattern.match(entry)
                if matched:
                    mult_l = int(matched.group(1))
                    mult_m = int(matched.group(2))
                    radius = float(matched.group(3))
                    # Read the actual data
                    a = data[entry][()].T
                    ts = timeseries.TimeSeries(a[0], a[1] + 1j * a[2])
                    alldets.append((mult_l, mult_m, radius, ts))

        return alldets
Exemple #18
0
    def test_extrapolation(self):

        # Test too many radii for the extrapolation
        with self.assertRaises(RuntimeError):
            self.gwdir._extrapolate_waves_to_infinity([1, 2], [1, 2], [1, 2],
                                                      1,
                                                      order=3)

        # Number of radii is not the same as waves
        with self.assertRaises(RuntimeError):
            self.gwdir._extrapolate_waves_to_infinity([1, 2], [1, 2],
                                                      [1, 2, 3],
                                                      1,
                                                      order=1)

        # TODO: Write real tests for these functions. Compare with POWER?

        # NOTE: These tests are not physical tests!

        # Smoke test, we just check that the code returns expected data types
        times = np.linspace(0, 2 * np.pi, 100)
        sins = ts.TimeSeries(times, np.sin(times))
        sins_plus_one = ts.TimeSeries(times, np.sin(times + 1))
        self.gwdir._extrapolate_waves_to_infinity(
            [sins, sins_plus_one],
            np.linspace(-12, -11, 100),
            [10, 11],
            1,
            order=1,
        )

        self.gwdir.extrapolate_strain_lm_to_infinity(2,
                                                     2,
                                                     0.1, [110.69, 110.69],
                                                     [2791, 2791.1],
                                                     order=0)
        self.gwdir.extrapolate_strain_lm_to_infinity(
            2,
            2,
            0.1,
            [110.69, 110.69],
            [2791, 2791.1],
            order=0,
            extrapolate_amplitude_phase=True,
        )
    def test_eq(self):

        times = np.linspace(0, 2 * np.pi, 100)
        values = np.sin(times)

        sins = ts.TimeSeries(times, values)

        self.assertFalse(sins == 1)
        self.assertTrue(self.TS == sins)
Exemple #20
0
    def test_phase_shift(self):

        sins = ts.TimeSeries(self.times, self.values)

        self.assertTrue(
            np.allclose(sins.phase_shifted(np.pi / 2).y, 1j * self.values))
        sins.phase_shift(np.pi / 2)

        self.assertTrue(np.allclose(sins.y, 1j * self.values))
    def test_align_maximum_minimum(self):
        t = np.linspace(0, 1, 100)

        tseries = ts.TimeSeries(t, t)

        # Should shift everything of -1, so new times should be from -1 to 0
        tseries.align_at_maximum()

        self.assertTrue(np.allclose(tseries.t, t - 1))

        # Minimum is at t = 1, notice we use the above t, so this is a line
        # that goes from -1 to 0. The absolute minimum is at the end.
        y2 = np.linspace(-1, 0, 100)
        tseries2 = ts.TimeSeries(t, y2)

        tseries2.align_at_minimum()

        self.assertTrue(np.allclose(tseries2.t, t - 1))
Exemple #22
0
    def test_WavesOneDet(self):

        t1 = np.linspace(0, np.pi, 100)
        t2 = np.linspace(2 * np.pi, 3 * np.pi, 100)
        y1 = np.sin(t1)
        y2 = np.sin(t2)
        ts1 = ts.TimeSeries(t1, y1)
        ts2 = ts.TimeSeries(t2, y2)
        dist1 = 100

        data1 = [(2, 2, ts1), (2, 2, ts2)]
        data2 = [(1, 1, ts1), (1, 0, ts2), (1, -1, ts2)]

        gw = cw.GravitationalWavesOneDet(dist1, data1)
        em = cw.ElectromagneticWavesOneDet(dist1, data2)

        self.assertEqual(gw.l_min, 2)
        self.assertEqual(em.l_min, 1)
    def test_remove_duplicate_iters(self):

        t = np.array([1, 2, 3, 4, 2, 3])
        y = np.array([0, 0, 0, 0, 0, 0])

        self.assertEqual(
            ts.remove_duplicate_iters(t, y),
            ts.TimeSeries([1, 2, 3], [0, 0, 0]),
        )
    def test_power(self):
        # Errors are tested by test_apply_binary

        times = np.linspace(0, 2 * np.pi, 100)
        values = np.array([2] * len(times))

        out = self.TS ** ts.TimeSeries(times, values)
        self.assertTrue(np.allclose(out.y, np.sin(times) ** 2))

        # Scalar
        out = self.TS ** 2
        self.assertTrue(np.allclose(out.y, np.sin(times) ** 2))

        # Test ipow
        out **= 2
        self.assertTrue(np.allclose(out.y, np.sin(times) ** 4))

        out **= ts.TimeSeries(times, values)
        self.assertTrue(np.allclose(out.y, np.sin(times) ** 8))
Exemple #25
0
    def test_mask_apply(self):

        ts_mask = ts.TimeSeries(self.times,
                                np.ma.masked_less_equal(self.times, 1))
        ts_nomask = ts.TimeSeries(self.times, self.times)

        ts_nomask.mask_apply(ts_mask.mask)

        self.assertEqual(ts_mask, ts_nomask)

        # Check with series already masked, by applying a second
        # mask

        ts_mask05 = ts.TimeSeries(self.times,
                                  np.ma.masked_less_equal(self.times, 0.5))

        ts_nomask.mask_apply(ts_mask05.mask)

        self.assertEqual(ts_mask05, ts_nomask)
    def test_time_shift(self):

        times = np.logspace(0, 1, 100)
        sins = ts.TimeSeries(times, self.values)

        self.assertTrue(np.allclose(sins.time_shifted(1).t, times + 1))

        sins.time_shift(1)

        self.assertTrue(np.allclose(sins.t, times + 1))
    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 test_mean_remove(self):

        # Check unevenly-space time
        times = np.logspace(0, 1, 100)
        sins = ts.TimeSeries(times, self.values + 1)

        self.assertTrue(np.allclose(sins.mean_removed().y, self.values))

        sins.mean_remove()

        self.assertTrue(np.allclose(sins.y, self.values))
Exemple #29
0
    def test_add(self):
        # Errors are tested by test_apply_binary

        times = np.linspace(0, 2 * np.pi, 100)
        values = np.sin(times)

        out = self.TS + ts.TimeSeries(times, values)
        self.assertTrue(np.allclose(out.y, 2 * np.sin(times)))

        # Scalar
        out = self.TS + 1
        self.assertTrue(np.allclose(out.y, 1 + np.sin(times)))

        out = 1 + self.TS
        self.assertTrue(np.allclose(out.y, 1 + np.sin(times)))

        # Test iadd
        out += 1
        self.assertTrue(np.allclose(out.y, 2 + np.sin(times)))

        out += self.TS
        self.assertTrue(np.allclose(out.y, 2 + 2 * np.sin(times)))

        # Everything should work with masked data too
        out = self.TS_cm + ts.TimeSeries(times, values)

        self.assertTrue(np.allclose(out.y, self.vals_cm + values))

        # Scalar
        out = self.TS_cm + 1
        self.assertTrue(np.allclose(out.y, 1 + self.vals_cm))

        out = 1 + self.TS_cm
        self.assertTrue(np.allclose(out.y, 1 + self.vals_cm))

        # Test iadd
        out += 1
        self.assertTrue(np.allclose(out.y, 2 + self.vals_cm))

        out += self.TS_cm
        self.assertTrue(np.allclose(out.y, 2 + 2 * self.vals_cm))
    def test_total_function_on_available_lm(self):

        # The two series must have the same times
        ts3 = ts.TimeSeries(self.t1, self.y2)
        data = [(2, 2, self.ts1), (1, -1, ts3)]
        mult = mp.MultipoleOneDet(100, data)

        # Test larger and smaller l
        with self.assertRaises(ValueError):
            mult.total_function_on_available_lm(lambda x: x, l_max=100)
        with self.assertRaises(ValueError):
            mult.total_function_on_available_lm(lambda x: x, l_max=0)

        # First, let's try with identity as function
        # The output should be just ts1 + ts2
        def identity(x, *args):
            return x

        self.assertEqual(
            mult.total_function_on_available_lm(identity),
            self.ts1 + ts3,
        )

        # Next, we use the l, m, r, information
        def func1(x, mult_l, mult_m, mult_r):
            return mult_l * mult_m * mult_r * x

        self.assertEqual(
            mult.total_function_on_available_lm(func1),
            2 * 2 * 100 * self.ts1 + 1 * (-1) * 100 * ts3,
        )

        # Next, we use args and kwargs
        def func2(x, mult_l, mult_m, mult_r, add, add2=0):
            return mult_l * mult_m * mult_r * x + add + add2

        # This will just add (add + add2) (2 + 3)
        self.assertEqual(
            mult.total_function_on_available_lm(func2, 2, add2=3),
            (2 * 2 * 100 * self.ts1 + 2 + 3 + 1 * (-1) * 100 * ts3 + 2 + 3),
        )

        # Finally, test l_max
        self.assertEqual(
            mult.total_function_on_available_lm(identity, l_max=1),
            ts3,
        )

        # Test no multiple moments
        with self.assertRaises(RuntimeError):
            mult._multipoles = {}
            mult.total_function_on_available_lm(lambda x: x, l_max=1)