def test_flat_dimensions_remove(self): geom = gd.UniformGrid([101, 1], x0=[0, 0], dx=[0.01, 1]) data = np.array([i * np.linspace(1, 5, 1) for i in range(101)]) ug_data = gd.UniformGridData(geom, data) ug_data.flat_dimensions_remove() flat_geom = gd.UniformGrid([101], x0=[0], x1=[1]) self.assertEqual( ug_data, gd.UniformGridData(flat_geom, np.linspace(0, 100, 101)) ) # Check invalidation of spline self.assertTrue(ug_data.invalid_spline) # Test from 3D to 2D grid_data3d = gdu.sample_function_from_uniformgrid( lambda x, y, z: x * (y + 2) * (z + 5), gd.UniformGrid([10, 20, 1], x0=[0, 1, 0], dx=[1, 1, 1]), ) grid_2d = gd.UniformGrid([10, 20], x0=[0, 1], dx=[1, 1]) expected_data2d = gdu.sample_function_from_uniformgrid( lambda x, y: x * (y + 2) * (0 + 5), grid_2d ) self.assertEqual( grid_data3d.flat_dimensions_removed(), expected_data2d )
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 test_partial_derivated(self): # Here we are also testing _call_component_method geom = gd.UniformGrid( [8001, 3], x0=[0, 0], x1=[2 * np.pi, 1], ref_level=0 ) geom2 = gd.UniformGrid( [10001, 3], x0=[0, 0], x1=[2 * np.pi, 1], ref_level=1 ) sin_wave1 = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom ) sin_wave2 = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom2 ) original_sin1 = sin_wave1.copy() original_sin2 = sin_wave2.copy() sin_wave = gd.HierarchicalGridData([sin_wave1] + [sin_wave2]) sin_copy = sin_wave.copy() # Second derivative should still be a -sin sin_wave.partial_derive(0, order=2) self.assertTrue( np.allclose(-sin_wave[0][0].data, original_sin1.data, atol=1e-3) ) self.assertTrue( np.allclose(-sin_wave[1][0].data, original_sin2.data, atol=1e-3) ) # Test _call_component_method with non-string name with self.assertRaises(TypeError): sin_wave._call_component_method(sin_wave) # Test _call_component_method with non existing method with self.assertRaises(ValueError): sin_wave._call_component_method("lol") gradient = sin_copy.gradient(order=2) # Along the first direction (it's a HierarchicalGridData) partial_x = gradient[0] self.assertTrue( np.allclose(-partial_x[0][0].data, original_sin1.data, atol=1e-3) ) # First refinement_level self.assertTrue( np.allclose(-partial_x[1][0].data, original_sin2.data, atol=1e-3) )
def test_merge_refinement_levels(self): # This also tests to_UniformGridData # We redefine this to be ref_level=1 grid1 = gd.UniformGrid([4, 5], x0=[0, 1], x1=[3, 5], ref_level=1) grid2 = gd.UniformGrid([11, 21], x0=[4, 6], x1=[14, 26], ref_level=1) grids = [grid1, grid2] # Here we use the same data with another big refinement level sampled # from the same function big_grid = gd.UniformGrid( [16, 26], x0=[0, 1], x1=[30, 51], ref_level=0 ) # Big grid has resolution 2 dx of grids def product(x, y): return x * (y + 2) grid_data_two_comp = [ gdu.sample_function_from_uniformgrid(product, g) for g in grids ] big_grid_data = gdu.sample_function_from_uniformgrid(product, big_grid) hg = gd.HierarchicalGridData(grid_data_two_comp + [big_grid_data]) # When I merge the data I should just get big_grid at the resolution # of self.grid_data_two_comp expected_grid = gd.UniformGrid( [31, 51], x0=[0, 1], x1=[30, 51], ref_level=-1 ) expected_data = gdu.sample_function_from_uniformgrid( product, expected_grid ) # Test with resample self.assertEqual( hg.merge_refinement_levels(resample=True), expected_data ) # If we don't resample there will be points that are "wrong" because we # compute them with the nearest neighbors of the lowest resolution grid # For example, the point with coordinate (5, 1) falls inside the lowest # resolution grid, so its value will be the value of the closest point # in big_grid (6, 1) -> 18. self.assertEqual(hg.merge_refinement_levels()((5, 1)), 18) self.assertEqual(hg.merge_refinement_levels().grid, expected_grid) # Test a case with only one refinement level, so just returning a copy hg_one = gd.HierarchicalGridData([big_grid_data]) self.assertEqual(hg_one.merge_refinement_levels(), big_grid_data)
def test_coordinates(self): def square(x, y): return x * (y + 2) grid_data = gdu.sample_function_from_uniformgrid(square, self.geom) self.assertTrue( np.allclose( grid_data.coordinates_from_grid()[0], self.geom.coordinates()[0], ) ) # This is a list of UniformGridData grids = grid_data.coordinates() # Here we check that they agree on two coordinates for dim in range(len(grids)): self.assertAlmostEqual( grids[dim](self.geom[2, 3]), self.geom[2, 3][dim] ) # Here we test coordiantes_meshgrid() self.assertTrue( np.allclose( grid_data.coordinates_meshgrid()[0], self.geom.coordinates()[0] ) )
def test_call_evalute_with_spline(self): # Teting call is the same as evalute_with_spline hg = gd.HierarchicalGridData(self.grid_data) # Test with multiple components hg3 = gd.HierarchicalGridData(self.grid_data_two_comp) # Scalar input self.assertAlmostEqual(hg((2, 3)), 10) self.assertAlmostEqual(hg3((2, 3)), 10) # Vector input in, vector input out self.assertEqual(hg([(2, 3)]).shape, (1,)) # Scalar input that pretends to be vector self.assertAlmostEqual(hg([(2, 3)]), 10) self.assertAlmostEqual(hg3([(2, 3)]), 10) # Vector input self.assertCountEqual(hg([(2, 3), (3, 2)]), [10, 12]) self.assertCountEqual(hg3([(2, 3), (3, 2)]), [10, 12]) def product(x, y): return x * (y + 2) # Uniform grid as input grid = gd.UniformGrid([3, 5], x0=[0, 1], x1=[2, 5]) grid_data = gdu.sample_function_from_uniformgrid(product, grid) self.assertTrue(np.allclose(hg3(grid), grid_data.data))
def test_init(self): # Test incorrect arguments # Not a list with self.assertRaises(TypeError): gd.HierarchicalGridData(0) # Empty list with self.assertRaises(ValueError): gd.HierarchicalGridData([]) # Not a list of UniformGridData with self.assertRaises(TypeError): gd.HierarchicalGridData([0]) # Inconsistent number of dimensions def product1(x): return x def product2(x, y): return x * y prod_data1 = gdu.sample_function(product1, [101], [0], [3]) prod_data2 = gdu.sample_function(product2, [101, 101], [0, 0], [3, 3]) with self.assertRaises(ValueError): gd.HierarchicalGridData([prod_data1, prod_data2]) # Only one component one = gd.HierarchicalGridData([prod_data1]) # Test content self.assertDictEqual( one.grid_data_dict, {-1: [prod_data1.ghost_zones_removed()]} ) grid = gd.UniformGrid([101], x0=[0], x1=[3], ref_level=2) # Two components at two different levels prod_data1_level2 = gdu.sample_function_from_uniformgrid( product1, grid ) two = gd.HierarchicalGridData([prod_data1, prod_data1_level2]) self.assertDictEqual( two.grid_data_dict, { -1: [prod_data1.ghost_zones_removed()], 2: [prod_data1_level2.ghost_zones_removed()], }, ) # Test a good grid hg_many_components = gd.HierarchicalGridData(self.grid_data) self.assertEqual( hg_many_components.grid_data_dict[0], [self.expected_data] ) # Test a grid with two separate components hg3 = gd.HierarchicalGridData(self.grid_data_two_comp) self.assertEqual(hg3.grid_data_dict[0], self.grid_data_two_comp)
def test_sample_function(self): # Test not grid as input with self.assertRaises(TypeError): gdu.sample_function_from_uniformgrid(np.sin, 0) # Test 1d geom = gd.UniformGrid(100, x0=0, x1=2 * np.pi) data = np.sin(np.linspace(0, 2 * np.pi, 100)) self.assertEqual( gdu.sample_function(np.sin, 100, 0, 2 * np.pi), gd.UniformGridData(geom, data), ) # Test with additional arguments geom_ref_level = gd.UniformGrid(100, x0=0, x1=2 * np.pi, ref_level=0) self.assertEqual( gdu.sample_function(np.sin, 100, 0, 2 * np.pi, ref_level=0), gd.UniformGridData(geom_ref_level, data), ) # Test 2d geom2d = gd.UniformGrid([100, 200], x0=[0, 1], x1=[1, 2]) def square(x, y): return x * y # Test function takes too few arguments with self.assertRaises(TypeError): gdu.sample_function_from_uniformgrid(lambda x: x, geom2d) # Test function takes too many arguments with self.assertRaises(TypeError): gdu.sample_function_from_uniformgrid(square, geom) # Test other TypeError with self.assertRaises(TypeError): gdu.sample_function_from_uniformgrid(np.sin, geom2d) data2d = np.vectorize(square)(*geom2d.coordinates(as_same_shape=True)) self.assertEqual( gdu.sample_function(square, [100, 200], [0, 1], [1, 2]), gd.UniformGridData(geom2d, data2d), ) self.assertEqual( gdu.sample_function_from_uniformgrid(square, geom2d), gd.UniformGridData(geom2d, data2d), )
def test__apply_binary(self): hg1 = gd.HierarchicalGridData(self.grid_data) # Test incompatible types with self.assertRaises(TypeError): hg1 + "hey" def neg_product(x, y): return -x * (y + 2) neg_data = gdu.sample_function_from_uniformgrid( neg_product, self.expected_grid ) hg2 = gd.HierarchicalGridData([neg_data]) zero = hg1 + hg2 zero += 0 # To check that zero is indeed zero we check that the abs max of the # data is 0 self.assertEqual(np.amax(np.abs(zero[0][0].data)), 0) # Test incompatible refinement levels neg_data_level2 = gdu.sample_function_from_uniformgrid( neg_product, self.expected_grid_level2 ) with self.assertRaises(ValueError): hg1 + gd.HierarchicalGridData([neg_data_level2]) # Test with multiple components hg3 = gd.HierarchicalGridData(self.grid_data_two_comp) hg4 = hg3.copy() hg4[0][0] *= -1 hg4[0][1] *= -1 zero2 = hg3 + hg4 self.assertEqual(np.amax(np.abs(zero2[0][0].data)), 0) self.assertEqual(np.amax(np.abs(zero2[0][1].data)), 0)
def test_finest_coarsest_level(self): geom = gd.UniformGrid( [81, 3], x0=[0, 0], x1=[2 * np.pi, 1], ref_level=0 ) geom2 = gd.UniformGrid( [11, 3], x0=[0, 0], x1=[2 * np.pi, 1], ref_level=1 ) sin_wave1 = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom ) sin_wave2 = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom2 ) sin_wave = gd.HierarchicalGridData([sin_wave1] + [sin_wave2]) self.assertEqual(sin_wave.finest_level, sin_wave2) self.assertEqual(sin_wave.coarsest_level, sin_wave1)
def setUp(self): # Here we split the rectangle with x0 = [0, 1], x1 = [14, 26] # and shape [14, 26] in 4 pieces grid1 = gd.UniformGrid([4, 5], x0=[0, 1], x1=[3, 5], ref_level=0) grid2 = gd.UniformGrid([11, 21], x0=[4, 6], x1=[14, 26], ref_level=0) grid3 = gd.UniformGrid([11, 5], x0=[4, 1], x1=[14, 5], ref_level=0) grid4 = gd.UniformGrid([4, 21], x0=[0, 6], x1=[3, 26], ref_level=0) self.grids0 = [grid1, grid2, grid3, grid4] # self.grids1 are not to be merged because they do not fill the space self.grids1 = [grid1, grid2] def product(x, y): return x * (y + 2) self.grid_data = [ gdu.sample_function_from_uniformgrid(product, g) for g in self.grids0 ] self.grid_data_two_comp = [ gdu.sample_function_from_uniformgrid(product, g) for g in self.grids1 ] self.expected_grid = gd.UniformGrid( [15, 26], x0=[0, 1], x1=[14, 26], ref_level=0 ) self.expected_data = gdu.sample_function_from_uniformgrid( product, self.expected_grid ) # We also consider one grid data with a different refinement level self.expected_grid_level2 = gd.UniformGrid( [15, 26], x0=[0, 1], x1=[14, 26], ref_level=2 ) self.expected_data_level2 = gdu.sample_function_from_uniformgrid( product, self.expected_grid_level2 )
def test_iter(self): hg1 = gd.HierarchicalGridData(self.grid_data) for ref_level, comp, data in hg1: self.assertTrue(isinstance(data, gd.UniformGridData)) self.assertEqual(ref_level, 0) self.assertEqual(comp, 0) hg3 = gd.HierarchicalGridData(self.grid_data_two_comp) comp_index = 0 for ref_level, comp, data in hg3: self.assertEqual(ref_level, 0) self.assertEqual(comp, comp_index) self.assertTrue(isinstance(data, gd.UniformGridData)) comp_index += 1 # Test from finest geom = gd.UniformGrid( [81, 3], x0=[0, 0], x1=[2 * np.pi, 1], ref_level=0 ) geom2 = gd.UniformGrid( [11, 3], x0=[0, 0], x1=[2 * np.pi, 1], ref_level=1 ) sin_wave1 = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom ) sin_wave2 = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom2 ) sin_wave = gd.HierarchicalGridData([sin_wave1] + [sin_wave2]) index = 1 for ref_level, comp, data in sin_wave.iter_from_finest(): self.assertEqual(ref_level, index) self.assertEqual(comp, 0) self.assertTrue(isinstance(data, gd.UniformGridData)) index -= 1
def test_slice(self): grid_data = gdu.sample_function_from_uniformgrid( lambda x, y, z: x * (y + 2) * (z + 5), gd.UniformGrid([10, 20, 30], x0=[0, 1, 2], dx=[1, 2, 0.1]), ) grid_data_copied = grid_data.copy() # Test cut is wrong dimension with self.assertRaises(ValueError): grid_data.slice([1, 2]) # Test no cut grid_data.slice([None, None, None]) self.assertEqual(grid_data, grid_data_copied) # Test cut point outside the grid with self.assertRaises(ValueError): grid_data.slice([1, 2, 1000]) # Test resample # Test cut along one dimension grid_data.slice([None, None, 3], resample=True) expected_no_z = gdu.sample_function_from_uniformgrid( lambda x, y: x * (y + 2) * (3 + 5), gd.UniformGrid([10, 20], x0=[0, 1], dx=[1, 2]), ) self.assertEqual(grid_data, expected_no_z) # Test no resample # # Reset grid data grid_data = grid_data_copied.copy() grid_data.slice([None, None, 3], resample=False) self.assertEqual(grid_data, expected_no_z)
def test_properties(self): def square(x, y): return x * (y + 2) grid_data = gdu.sample_function_from_uniformgrid(square, self.geom) self.assertCountEqual(grid_data.x0, self.geom.x0) self.assertCountEqual(grid_data.origin, self.geom.x0) self.assertCountEqual(grid_data.shape, self.geom.shape) self.assertCountEqual(grid_data.x1, self.geom.x1) self.assertCountEqual(grid_data.dx, self.geom.dx) self.assertCountEqual(grid_data.delta, self.geom.dx) self.assertCountEqual(grid_data.num_ghost, self.geom.num_ghost) self.assertEqual(grid_data.ref_level, self.geom.ref_level) self.assertEqual(grid_data.component, self.geom.component) self.assertEqual(grid_data.time, self.geom.time) self.assertEqual(grid_data.iteration, self.geom.iteration) self.assertTrue(np.allclose(grid_data.data_xyz, grid_data.data.T))
def test__apply_unary(self): hg1 = gd.HierarchicalGridData(self.grid_data) def neg_product(x, y): return -x * (y + 2) neg_data = gdu.sample_function_from_uniformgrid( neg_product, self.expected_grid ) hg2 = gd.HierarchicalGridData([neg_data]) self.assertEqual(-hg1, hg2) # Test with multiple components hg3 = gd.HierarchicalGridData(self.grid_data_two_comp) hg4 = hg3.copy() hg4[0][0] *= -1 hg4[0][1] *= -1 self.assertEqual(-hg3, hg4)
def test_partial_derive(self): geom = gd.UniformGrid([8001, 3], x0=[0, 0], x1=[2 * np.pi, 1]) sin_wave = gdu.sample_function_from_uniformgrid( lambda x, y: np.sin(x), geom ) original_sin = sin_wave.copy() # Error dimension not found with self.assertRaises(ValueError): sin_wave.partial_derived(5) # Second derivative should still be a -sin sin_wave.partial_derive(0, order=2) self.assertTrue( np.allclose(-sin_wave.data, original_sin.data, atol=1e-3) ) gradient = original_sin.gradient(order=2) self.assertTrue( np.allclose(-gradient[0].data, original_sin.data, atol=1e-3) )
def test_resampled(self): def product(x, y): return x * (y + 2) def product_complex(x, y): return (1 + 1j) * x * (y + 2) prod_data = gdu.sample_function(product, [101, 201], [0, 1], [3, 4]) prod_data_complex = gdu.sample_function( product_complex, [3001, 2801], [0, 1], [3, 4] ) # Check error with self.assertRaises(TypeError): prod_data.resampled(2) # Check same grid self.assertEqual(prod_data.resampled(prod_data.grid), prod_data) new_grid = gd.UniformGrid([51, 101], x0=[1, 2], x1=[2, 3]) resampled = prod_data_complex.resampled(new_grid) exp_resampled = gdu.sample_function_from_uniformgrid( product_complex, new_grid ) self.assertEqual(resampled.grid, new_grid) self.assertTrue(np.allclose(resampled.data, exp_resampled.data)) # Check that the method of the spline is linear self.assertEqual(prod_data_complex.spline_imag.method, "linear") # Test using nearest interpolation resampled_nearest = prod_data_complex.resampled( new_grid, piecewise_constant=True ) self.assertTrue( np.allclose(resampled_nearest.data, exp_resampled.data, atol=1e-3) ) # Check that the method of the spline hasn't linear self.assertEqual(prod_data_complex.spline_imag.method, "linear") # Check single number self.assertAlmostEqual(resampled_nearest((2, 2.5)), 9 * (1 + 1j)) # Check with one point new_grid2 = gd.UniformGrid([11, 1], x0=[1, 2], dx=[0.1, 1]) resampled2 = prod_data_complex.resampled(new_grid2) prod_data_one_point = gdu.sample_function_from_uniformgrid( product_complex, new_grid2 ) self.assertEqual(resampled2, prod_data_one_point) # Resample from 3d to 2d grid_data3d = gdu.sample_function_from_uniformgrid( lambda x, y, z: x * (y + 2) * (z + 5), gd.UniformGrid([10, 20, 11], x0=[0, 1, 0], dx=[1, 2, 0.1]), ) grid_2d = gd.UniformGrid([10, 20, 1], [0, 1, 0], dx=[1, 2, 0.1]) expected_data2d = gdu.sample_function_from_uniformgrid( lambda x, y, z: x * (y + 2) * (z + 5), grid_2d ) self.assertEqual(grid_data3d.resampled(grid_2d), expected_data2d)
def test_splines(self): # Let's start with 1d. sin_data = gdu.sample_function(np.sin, 12000, 0, 2 * np.pi) sin_data_complex = sin_data + 1j * sin_data # Test unknown ext with self.assertRaises(ValueError): sin_data.evaluate_with_spline(1, ext=3) # Test k!=0!=1 with self.assertRaises(ValueError): sin_data._make_spline(k=3) self.assertAlmostEqual( sin_data_complex.evaluate_with_spline([np.pi / 3]), (1 + 1j) * np.sin(np.pi / 3), ) # Test with point in cell but outside boundary, in # We change the boundary values to be different from 0 sin_data_complex_plus_one = sin_data_complex + 1 + 1j dx = sin_data_complex.dx[0] # At the boundary, we do a constant extrapolation, so the value should # be the boundary value self.assertAlmostEqual( sin_data_complex_plus_one.evaluate_with_spline([0 - 0.25 * dx]), (1 + 1j), ) self.assertAlmostEqual( sin_data_complex_plus_one.evaluate_with_spline( [2 * np.pi + 0.25 * dx] ), (1 + 1j), ) # Test __call__ self.assertAlmostEqual( sin_data_complex([np.pi / 3]), (1 + 1j) * np.sin(np.pi / 3), ) # Test on a point of the grid point = [sin_data.grid.coordinates_1d[0][2]] self.assertAlmostEqual( sin_data_complex(point), (1 + 1j) * np.sin(point[0]), ) # Test on a point outside the grid with the lookup table with self.assertRaises(ValueError): sin_data_complex._nearest_neighbor_interpolation( np.array([1000]), ext=2, ), # Test on a point outside the grid with the lookup table and # ext = 1 self.assertEqual( sin_data_complex.evaluate_with_spline( [1000], ext=1, piecewise_constant=True ), 0, ) # Vector input self.assertTrue( np.allclose( sin_data_complex.evaluate_with_spline( [[np.pi / 3], [np.pi / 4]] ), np.array( [ (1 + 1j) * np.sin(np.pi / 3), (1 + 1j) * np.sin(np.pi / 4), ] ), ) ) # Vector input in, vector input out self.assertEqual(sin_data_complex([[1]]).shape, (1,)) # Now 2d def product(x, y): return x * (y + 2) prod_data = gdu.sample_function(product, [101, 101], [0, 0], [3, 3]) prod_data_complex = (1 + 1j) * prod_data self.assertAlmostEqual( prod_data_complex.evaluate_with_spline((2, 3)), (1 + 1j) * 10, ) # Vector input self.assertTrue( np.allclose( prod_data_complex.evaluate_with_spline([(1, 0), (2, 3)]), np.array([(1 + 1j) * 2, (1 + 1j) * 10]), ) ) self.assertTrue( np.allclose( prod_data_complex.evaluate_with_spline( [[(1, 0), (2, 3)], [(3, 1), (0, 0)]] ), np.array([[(1 + 1j) * 2, (1 + 1j) * 10], [(1 + 1j) * 9, 0]]), ) ) # Real data self.assertAlmostEqual( prod_data.evaluate_with_spline((2, 3)), 10, ) # Extrapolate outside self.assertAlmostEqual( prod_data.evaluate_with_spline((20, 20), ext=1), 0 ) self.assertAlmostEqual( prod_data_complex.evaluate_with_spline((20, 20), ext=1), 0 ) self.assertTrue(prod_data_complex.spline_real.bounds_error) self.assertTrue(prod_data_complex.spline_imag.bounds_error) # Test on a UniformGrid sin_data = gdu.sample_function(np.sin, 12000, 0, 2 * np.pi) linspace = gd.UniformGrid(101, x0=0, x1=3) output = sin_data(linspace) self.assertTrue( np.allclose(output.data, np.sin(linspace.coordinates())) ) # Incompatible dimensions with self.assertRaises(ValueError): sin_data(gd.UniformGrid([101, 201], x0=[0, 1], x1=[3, 4])) # Test with grid that has a flat dimension prod_data_flat = gdu.sample_function_from_uniformgrid( product, gd.UniformGrid([101, 1], x0=[0, 0], dx=[1, 3]) ) # y = 1 is in the flat cell, where y = 0, and here we are using nearest # interpolation self.assertAlmostEqual(prod_data_flat((1, 1)), 2) # Vector self.assertCountEqual(prod_data_flat([(1, 1), (2, 1)]), [2, 4])