From a46a9fbf81431d654a9db775464880adb6280996 Mon Sep 17 00:00:00 2001 From: Radonirinaunimi Date: Mon, 21 Oct 2024 00:22:12 +0200 Subject: [PATCH] Improve Python tests and their coverage --- pineappl_py/src/evolution.rs | 14 ++++ pineappl_py/src/grid.rs | 56 -------------- pineappl_py/src/subgrid.rs | 10 +++ pineappl_py/tests/conftest.py | 43 ++++++++++- pineappl_py/tests/test_evolution.py | 14 +++- pineappl_py/tests/test_fk_table.py | 105 ++++++++++++------------- pineappl_py/tests/test_grid.py | 115 ++++++++++++++++++++++++++-- pineappl_py/tests/test_subgrid.py | 16 +++- 8 files changed, 254 insertions(+), 119 deletions(-) diff --git a/pineappl_py/src/evolution.rs b/pineappl_py/src/evolution.rs index e4c444e4..efa0fca0 100644 --- a/pineappl_py/src/evolution.rs +++ b/pineappl_py/src/evolution.rs @@ -72,6 +72,20 @@ pub struct PyEvolveInfo { #[pymethods] impl PyEvolveInfo { + /// Constructor. + #[new] + #[must_use] + pub const fn new(fac1: Vec, pids1: Vec, x1: Vec, ren1: Vec) -> Self { + Self { + evolve_info: EvolveInfo { + fac1, + pids1, + x1, + ren1, + }, + } + } + /// Squared factorization scales of the `Grid`. #[getter] fn fac1<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { diff --git a/pineappl_py/src/grid.rs b/pineappl_py/src/grid.rs index 5a1d6488..225836cf 100644 --- a/pineappl_py/src/grid.rs +++ b/pineappl_py/src/grid.rs @@ -319,62 +319,6 @@ impl PyGrid { .unwrap()) } - /// Evolve grid with one single EKO. - /// - /// # Panics - /// TODO - /// - /// # Errors - /// TODO - /// - /// Parameters - /// ---------- - /// slices : Iterable - /// list of (PyOperatorSliceInfo, 5D array) describing each convolution - /// order_mask : numpy.ndarray(bool) - /// boolean mask to activate orders - /// xi : (float, float) - /// factorization and renormalization variation - /// ren1 : numpy.ndarray(float) - /// list of renormalization scales - /// alphas : numpy.ndarray(float) - /// list with :math:`\alpha_s(Q2)` for the process scales - /// - /// Returns - /// ------- - /// PyFkTable : - /// produced FK table - pub fn evolve_with_slice_iter<'py>( - &self, - slices: &Bound<'py, PyIterator>, - order_mask: Vec, - xi: (f64, f64, f64), - ren1: Vec, - alphas: Vec, - ) -> PyResult { - Ok(self - .grid - .evolve( - vec![slices.into_iter().map(|slice| { - let (info, op) = slice - .unwrap() - .extract::<(PyOperatorSliceInfo, PyReadonlyArray4)>() - .unwrap(); - Ok::<_, std::io::Error>(( - info.info, - // TODO: avoid copying - CowArray::from(op.as_array().to_owned()), - )) - })], - &order_mask, - xi, - &AlphasTable { ren1, alphas }, - ) - .map(|fk_table| PyFkTable { fk_table }) - // TODO: avoid unwrap and convert `Result` into `PyResult` - .unwrap()) - } - /// Load from file. /// /// # Panics diff --git a/pineappl_py/src/subgrid.rs b/pineappl_py/src/subgrid.rs index f66aabba..c47e5796 100644 --- a/pineappl_py/src/subgrid.rs +++ b/pineappl_py/src/subgrid.rs @@ -50,6 +50,16 @@ impl PyMu2 { fn set_fac(&mut self, value: f64) { self.mu2.fac = value; } + + #[getter] + const fn frg(&self) -> f64 { + self.mu2.frg + } + + #[setter] + fn set_frg(&mut self, value: f64) { + self.mu2.frg = value; + } } /// PyO3 wrapper to :rustdoc:`pineappl::subgrid::SubgridEnum ` diff --git a/pineappl_py/tests/conftest.py b/pineappl_py/tests/conftest.py index 12887d41..3561254d 100644 --- a/pineappl_py/tests/conftest.py +++ b/pineappl_py/tests/conftest.py @@ -1,20 +1,57 @@ import pytest +import subprocess class PDF: - def xfxQ(self, pid, x, q): - return self.xfxQ2(pid, x, q**2) - def xfxQ2(self, pid, x, q2): if pid in range(-6, 6): return x * (1 - x) else: return 0.0 + def xfxQ(self, pid, x, q): + return self.xfxQ2(pid, x, q**2) + def alphasQ(self, q): return 1.0 + # Define the Toy Polarized PDF set + def polarized_pdf(self, pid, x, q2): + return 2.0 + + # Define the Toy Unpolarized PDF set + def unpolarized_pdf(self, pid, x, q2): + return 1.0 + @pytest.fixture def pdf(): return PDF() + + +@pytest.fixture +def download_objects(tmp_path_factory): + def _download_fk(objname: str) -> None: + download_dir = tmp_path_factory.mktemp("data") + file_path = download_dir / f"{objname}" + args = [ + "wget", + "--no-verbose", + "--no-clobber", + "-P", + f"{download_dir}", + f"https://data.nnpdf.science/pineappl/test-data/{objname}", + ] + + try: + _ = subprocess.run( + args, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return file_path + except OSError as error: + msg = f"Failed to execute the command {args}." + raise EnvironmentError(msg) from error + + return _download_fk diff --git a/pineappl_py/tests/test_evolution.py b/pineappl_py/tests/test_evolution.py index f5627ad3..bc25abec 100644 --- a/pineappl_py/tests/test_evolution.py +++ b/pineappl_py/tests/test_evolution.py @@ -9,7 +9,7 @@ import numpy as np from pineappl.boc import Channel, Kinematics from pineappl.convolutions import Conv, ConvType -from pineappl.evolution import OperatorSliceInfo +from pineappl.evolution import OperatorSliceInfo, EvolveInfo from pineappl.grid import Grid, Order from pineappl.interpolation import Interp from pineappl.pids import PidBasis @@ -61,6 +61,18 @@ def fake_grid( kinematics=kinematics, ) + def test_evolveinfo(self): + evinfo = EvolveInfo( + fac1=[0.5, 1.0, 2.0], + pids1=[-2, 0, 2], + x1=[1e-3, 0.5, 1], + ren1=[0.5, 1.0, 2.0], + ) + np.testing.assert_array_equal(evinfo.fac1, [0.5, 1.0, 2.0]) + np.testing.assert_array_equal(evinfo.pids1, [-2, 0, 2]) + np.testing.assert_array_equal(evinfo.x1, [1e-3, 0.5, 1.0]) + np.testing.assert_array_equal(evinfo.fac1, [0.5, 1.0, 2.0]) + def test_with_one_eko(self): # Define convolution types and the initial state hadrons # We consider an initial state Polarized Proton diff --git a/pineappl_py/tests/test_fk_table.py b/pineappl_py/tests/test_fk_table.py index f32f4340..fdde8d4f 100644 --- a/pineappl_py/tests/test_fk_table.py +++ b/pineappl_py/tests/test_fk_table.py @@ -5,11 +5,10 @@ """ import numpy as np -import pytest -import subprocess +import tempfile from pineappl.boc import Channel, Kinematics from pineappl.convolutions import Conv, ConvType -from pineappl.fk_table import FkTable +from pineappl.fk_table import FkTable, FkAssumptions from pineappl.grid import Grid, Order from pineappl.import_subgrid import ImportSubgridV1 from pineappl.interpolation import Interp @@ -17,34 +16,6 @@ from typing import List -@pytest.fixture -def download_fktable(tmp_path_factory): - def _download_fk(fkname: str) -> None: - download_dir = tmp_path_factory.mktemp("data") - file_path = download_dir / f"{fkname}" - args = [ - "wget", - "--no-verbose", - "--no-clobber", - "-P", - f"{download_dir}", - f"https://data.nnpdf.science/pineappl/test-data/{fkname}", - ] - - try: - _ = subprocess.run( - args, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - return file_path - except OSError as error: - msg = f"Failed to execute the command {args}." - raise EnvironmentError(msg) from error - - return _download_fk - - class TestFkTable: def fake_grid( self, @@ -121,7 +92,11 @@ def test_convolve(self): np.array([1.0]), ) g.set_subgrid(0, 0, 0, subgrid.into()) - fk = FkTable(g) # Convert Grid -> FkTable + + # Convert the Grid -> FkTable + fk = FkTable(g) + + # Test a simple convolution of the FK table np.testing.assert_allclose( fk.convolve( pdg_convs=[h_conv], @@ -137,38 +112,72 @@ def test_convolve(self): [5e7 / 9999, 0.0], ) + # Test writing/dumping the FK table into disk + with tempfile.TemporaryDirectory() as tmpdir: + fk.write(f"{tmpdir}/toy_fktable.pineappl") + fk.write_lz4(f"{tmpdir}/toy_fktable.pineappl.lz4") + + def test_fktable( + self, + download_objects, + fkname: str = "FKTABLE_CMSTTBARTOT8TEV-TOPDIFF8TEVTOT.pineappl.lz4", + ): + fk_table = download_objects(f"{fkname}") + fk = FkTable.read(fk_table) + + assert fk.table().shape == (1, 51, 34, 34) + np.testing.assert_allclose(fk.muf2(), 2.7224999999999997) + + # Check the various aspects of the Bins + assert fk.bins() == 1 + np.testing.assert_allclose(fk.bin_normalizations(), [1.0]) + np.testing.assert_allclose(fk.bin_right(dimension=0), [1.0]) + np.testing.assert_allclose(fk.bin_left(dimension=0), [0.0]) + + # Check the various aspects of the Channels + channels = fk.channels() + assert len(channels) == 51 + assert [21, 200] in channels + + # Check the contents of the x-grid + x_grid = fk.x_grid() + assert x_grid.size == 34 + np.testing.assert_allclose(x_grid[0], 1.57456056e-04) + + # Test FK optimization + assumption = FkAssumptions("Nf6Sym") + fk.optimize(assumption) + def test_unpolarized_convolution( self, - download_fktable, - fkname: str = "CMSTTBARTOT8TEV-TOPDIFF8TEVTOT.pineappl.lz4", + pdf, + download_objects, + fkname: str = "FKTABLE_CMSTTBARTOT8TEV-TOPDIFF8TEVTOT.pineappl.lz4", ): """Check the convolution of an actual FK table that involves two symmetrical unpolarized protons: """ expected_results = [3.72524538e04] - fk_table = download_fktable(f"{fkname}") + fk_table = download_objects(f"{fkname}") fk = FkTable.read(fk_table) # Convolution object of the 1st hadron - Polarized h = ConvType(polarized=False, time_like=False) h_conv = Conv(conv_type=h, pid=2212) - # Define the Toy Unpolarized PDF set - def _unpolarized_pdf(pid, x, q2): - return 1.0 - np.testing.assert_allclose( fk.convolve( pdg_convs=[h_conv, h_conv], - xfxs=[_unpolarized_pdf, _unpolarized_pdf], + xfxs=[pdf.unpolarized_pdf, pdf.unpolarized_pdf], ), expected_results, ) def test_polarized_convolution( self, - download_fktable, - fkname: str = "GRID_STAR_WMWP_510GEV_WM-AL-POL.pineappl.lz4", + pdf, + download_objects, + fkname: str = "FKTABLE_STAR_WMWP_510GEV_WM-AL-POL.pineappl.lz4", ): """Check the convolution of an actual FK table that involves two different initial states: @@ -183,7 +192,7 @@ def test_polarized_convolution( -5.67594297e5, +6.59245015e4, ] - fk_table = download_fktable(f"{fkname}") + fk_table = download_objects(f"{fkname}") fk = FkTable.read(fk_table) # Convolution object of the 1st hadron - Polarized @@ -194,18 +203,10 @@ def test_polarized_convolution( h2 = ConvType(polarized=False, time_like=False) h2_conv = Conv(conv_type=h2, pid=2212) - # Define the Toy Polarized PDF set - def _polarized_pdf(pid, x, q2): - return 2.0 - - # Define the Toy Unpolarized PDF set - def _unpolarized_pdf(pid, x, q2): - return 1.0 - np.testing.assert_allclose( fk.convolve( pdg_convs=[h1_conv, h2_conv], - xfxs=[_polarized_pdf, _unpolarized_pdf], + xfxs=[pdf.polarized_pdf, pdf.unpolarized_pdf], ), expected_results, ) diff --git a/pineappl_py/tests/test_grid.py b/pineappl_py/tests/test_grid.py index 9160cb11..10373eab 100644 --- a/pineappl_py/tests/test_grid.py +++ b/pineappl_py/tests/test_grid.py @@ -1,5 +1,6 @@ import numpy as np import pytest +import tempfile from pineappl.bin import BinRemapper from pineappl.boc import Channel, Kinematics @@ -151,6 +152,14 @@ def test_channels(self): assert len(g.channels()) == 1 assert g.channels()[0] == UP_ANTIUP_CHANNEL + def test_write(self): + g = self.fake_grid() + + # Test writing/dumping the FK table into disk + with tempfile.TemporaryDirectory() as tmpdir: + g.write(f"{tmpdir}/toy_grid.pineappl") + g.write_lz4(f"{tmpdir}/toy_grid.pineappl.lz4") + def test_set_subgrid(self): g = self.fake_grid() @@ -195,6 +204,24 @@ def test_bins(self): np.testing.assert_allclose(g.bin_left(1), [2, 3]) np.testing.assert_allclose(g.bin_right(1), [3, 5]) + def test_grid( + self, + download_objects, + gridname: str = "GRID_STAR_WMWP_510GEV_WP-AL-POL.pineappl.lz4", + ): + grid = download_objects(f"{gridname}") + g = Grid.read(grid) + + # Get the types of convolutions for this grid + for conv in g.convolutions(): + assert isinstance(conv, Conv) + + # Check that the scalings work, ie run without error + # TODO: implement method to check the actual values + g.scale(factor=10.0) + g.scale_by_bin(factors=[10.0, 20.0]) + g.delete_bins(bin_indices=[0, 1, 2]) + @pytest.mark.parametrize("params,expected", TESTING_SPECS) def test_toy_convolution(self, params, expected): g = self.fake_grid() @@ -213,13 +240,74 @@ def test_toy_convolution(self, params, expected): # Check the convolutions of the GRID np.testing.assert_allclose(g.convolve(**params), expected) - def test_unpolarized_convolution(self): - # TODO - pass + def test_unpolarized_convolution( + self, + pdf, + download_objects, + gridname: str = "GRID_DYE906R_D_bin_1.pineappl.lz4", + ): + """Tes convolution with an actual Grid. In the following example, + it is a DIS grid that involves a single unique hadron/proton. + """ + expected_results = [ + +3.71019208e4, + +3.71019208e4, + +2.13727492e4, + -1.83941398e3, + +3.22728612e3, + +5.45646897e4, + ] + + grid = download_objects(f"{gridname}") + g = Grid.read(grid) + + # Convolution object of the Unpolarized proton + h = ConvType(polarized=False, time_like=False) + h_conv = Conv(conv_type=h, pid=2212) - def test_polarized_convolution(self): - # TODO - pass + np.testing.assert_allclose( + g.convolve( + pdg_convs=[h_conv], # Requires ONE single convolutions + xfxs=[pdf.polarized_pdf], # Requires ONE single PDF + alphas=pdf.alphasQ, + ), + expected_results, + ) + + def test_polarized_convolution( + self, + pdf, + download_objects, + gridname: str = "GRID_STAR_WMWP_510GEV_WP-AL-POL.pineappl.lz4", + ): + expected_results = [ + +5.50006832e6, + +1.68117895e6, + +3.08224445e5, + -2.65602464e5, + -1.04664085e6, + -5.19002089e6, + ] + + grid = download_objects(f"{gridname}") + g = Grid.read(grid) + + # Convolution object of the 1st hadron - Polarized + h1 = ConvType(polarized=True, time_like=False) + h1_conv = Conv(conv_type=h1, pid=2212) + + # Convolution object of the 2nd hadron - Unpolarized + h2 = ConvType(polarized=False, time_like=False) + h2_conv = Conv(conv_type=h2, pid=2212) + + np.testing.assert_allclose( + g.convolve( + pdg_convs=[h1_conv, h2_conv], + xfxs=[pdf.polarized_pdf, pdf.unpolarized_pdf], + alphas=pdf.alphasQ, + ), + expected_results, + ) def test_io(self, tmp_path): g = self.fake_grid() @@ -276,3 +364,18 @@ def test_merge(self): with pytest.raises(ValueError, match="NonConsecutiveBins"): g2.merge(g5) + + def test_evolveinfo( + self, + download_objects, + gridname: str = "GRID_STAR_WMWP_510GEV_WP-AL-POL.pineappl.lz4", + ): + grid = download_objects(f"{gridname}") + g = Grid.read(grid) + g_evinfo = g.evolve_info(order_mask=[True, False, False, False]) + + np.testing.assert_allclose(g_evinfo.fac1, [6463.838404]) + np.testing.assert_allclose(g_evinfo.ren1, [6463.838404]) + np.testing.assert_allclose(g_evinfo.pids1, [-5, -3, -1, 2, 4]) + assert g_evinfo.x1.size == 23 + np.testing.assert_allclose(g_evinfo.x1[0], 0.01437507) diff --git a/pineappl_py/tests/test_subgrid.py b/pineappl_py/tests/test_subgrid.py index 5c2dd3b2..1b1700fe 100644 --- a/pineappl_py/tests/test_subgrid.py +++ b/pineappl_py/tests/test_subgrid.py @@ -6,7 +6,7 @@ from pineappl.import_subgrid import ImportSubgridV1 from pineappl.interpolation import Interp from pineappl.pids import PidBasis -from pineappl.subgrid import SubgridEnum +from pineappl.subgrid import SubgridEnum, Mu2 # Define some default for the minimum value of `Q2` Q2_MIN = 1e2 @@ -109,6 +109,20 @@ def fake_importonlysubgrid(self) -> tuple: ) return subgrid, [x1s, x2s, scale, array] + def test_mu2(self): + mu2_obj = Mu2(ren=1.0, fac=2.0, frg=0.0) + assert mu2_obj.ren == 1.0 + assert mu2_obj.fac == 2.0 + assert mu2_obj.frg == 0.0 + + # Overwrite the constructed values + mu2_obj.ren = 10.0 + mu2_obj.fac = 20.0 + mu2_obj.frg = 10.0 + assert mu2_obj.ren == 10.0 + assert mu2_obj.fac == 20.0 + assert mu2_obj.frg == 10.0 + def test_subgrid_methods(self): # TODO: extract the values of the scales and x grids grid = self.fake_grid()