Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better basf2-related docs, examples and change how basf2 release hash is calculated for local releases. #193

Merged
merged 15 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## Unreleased

## Changed

- For local basf2 versions, change how hash for `basf2_release` Parameter is calculated. Now use basf2 functionality to get the version, to be consistent with the output of `basf2 --version`. The new hash encodes both the local and central basf2 release, the basf2 function [`getCommitID`](https://github.com/belle2/basf2/blob/1c972b2c89ef11f38ee2f5ea8eb562dde0637155/framework/io/include/RootIOUtilities.h#L77-L84). When basf2 is not set up, print warning before returning `"not_set"`. Thanks to @GiacomoXT in [#193](https://github.com/nils-braun/b2luigi/issues/193).

**Warning:** If you use local basf2 versions, that is your `basf2_release` is a git hash, this will change your b2luigi target output paths. This means that tasks that were marked _complete_, might suddenly not be _complete_ anymore after updating to this release. A workaround is to check for the new expected path via `python3 <steering_fname>.py --show_output` and rename the `git_hash=<…>` directory.

- Apply `max_events` Parameter not by changing the environment singleton, but instead forward it to `basf2.process` call. This should hopefully not affect the behaviour in practice. Also by @GiacomoXT in [#193](https://github.com/nils-braun/b2luigi/issues/193)

- Refactor the basf2 related examples to use more idiomatic, modern basf2 code, e.g. using `basf2.Path()` instead of `basf2.create_path()`. . Also by @GiacomoXT in [#193](https://github.com/nils-braun/b2luigi/issues/193)

## Fixed

- Fix example `SimulationTask` task in `basf2_chain_example.py`, which probably wasn't working as it was missing the Geometry module. Also by @GiacomoXT in [#193](https://github.com/nils-braun/b2luigi/issues/193)


**Full Changelog**: https://github.com/nils-braun/b2luigi/compare/v0.9.1...main

## [0.9.1] - 2023-03-20

### Fixed
Expand All @@ -19,7 +36,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add the ability to pass a custom hashing function to parameters via the `hash_function` keyword argument. The function must take one argument, the value of the parameter. It is up to the user to ensure unique strings are created. [#189](https://github.com/nils-braun/b2luigi/pull/189)
- **gbasf2**: Switch to the `--new` flag in `gb2_ds_get` which downloads files significantly faster than previously. Gbasf2 release v5r6 (November 2022) is required. [#190](https://github.com/nils-braun/b2luigi/pull/190).

**Full Changelog**: https://github.com/nils-braun/b2luigi/compare/v0.9.1...v0.9.0
**Full Changelog**: https://github.com/nils-braun/b2luigi/compare/v0.9.0...v0.9.1

## [0.9.0] - 2023-03-20

Expand Down
9 changes: 3 additions & 6 deletions b2luigi/basf2_helper/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,18 @@ def process(self):

try:
import basf2
import ROOT
except ImportError:
raise ImportError("Can not find ROOT or basf2. Can not use the basf2 task.")
raise ImportError("Can not find basf2. Can not use the basf2 task.")

if self.num_processes:
basf2.set_nprocesses(self.num_processes)

if self.max_event:
ROOT.Belle2.Environment.Instance().setNumberEventsOverride(self.max_event)

path = self.create_path()

path.add_module("Progress")
basf2.print_path(path)
basf2.process(path)
max_event = self.max_event if self.max_event else 0
basf2.process(path=path, max_event=max_event)

print(basf2.statistics)

Expand Down
32 changes: 23 additions & 9 deletions b2luigi/basf2_helper/utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import os

import git
import warnings


def get_basf2_git_hash():
basf2_release = os.getenv("BELLE2_RELEASE")
"""Return name of basf2 release or if local basf2 is used its version hash.

if basf2_release == "head" or basf2_release is None:
basf2_release_location = os.getenv("BELLE2_LOCAL_DIR")
The version is equivalent to the version returned by ``basf2 --version``.

if basf2_release_location:
return git.Repo(basf2_release_location).head.object.hexsha
return "not_set"
Returns ``\"not set\"``, if no basf2 release is set and basf2 cannot be imported.
"""
basf2_release = os.getenv("BELLE2_RELEASE")

return basf2_release
if basf2_release not in ("head", None):
return basf2_release
# else if BASF2_RELEASE environment variable is not set, get version hash of
# local basf2
try:
import basf2.version
# try both get_version method and version attribute, one of which
# should work depending on basf2 release
if hasattr(basf2.version, "get_version"):
return basf2.version.get_version()
return basf2.version.version
except ImportError as err:
warnings.warn(
f"No basf2 was found. Setting basf2 git hash to \"not_set\": \n {err}",
category=ImportWarning,
)
return "not_set"
2 changes: 1 addition & 1 deletion docs/advanced/basf2-examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ nTuple Generation
# (parameters just examples)
input_file_names = ..

path = basf2.create_path()
path = basf2.Path()
modularAnalysis.inputMdstList('default', input_file_names, path=path)

# Now fill your particle lists, just examples
Expand Down
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ Features, fixing, help and testing
* Artur Gottmann (`ArturAkh`_)
* Caspar Schmitt (`schmitca`_)
* Marcel Hohmann (`MarcelHoh_`)
* Giacomo De Pietro (`GiacomoXT_`)

Stolen ideas
* Implementation of SGE batch system (`sge`_).
Expand All @@ -159,6 +160,7 @@ Stolen ideas
.. _`mschnepf`: https://github.com/mschnepf
.. _`schmitca`: https://github.com/schmitca
.. _`MarcelHoh`: https://github.com/MarcelHoh
.. _`GiacomoXT`: https://github.com/GiacomoXT
.. _`sge`: https://github.com/spotify/luigi/blob/master/luigi/contrib/sge.py
.. _`lsf`: https://github.com/spotify/luigi/pull/2373/files

Expand Down
30 changes: 18 additions & 12 deletions examples/basf2/basf2_chain_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import vertex
import generators
import reconstruction
from ROOT import Belle2
import mdst


class SimulationType(Enum):
Expand All @@ -23,23 +23,27 @@ class SimulationTask(Basf2PathTask):
event_type = luigi.EnumParameter(enum=SimulationType)

def create_path(self):
path = basf2.create_path()
path = basf2.Path()
modularAnalysis.setupEventInfo(self.n_events, path)

if self.event_type == SimulationType.y4s:
# in current main branch and release 5 the Y(4S)decay file is moved, so try old and new locations
find_file_ignore_error = True
dec_file = Belle2.FileSystem.findFile('analysis/examples/tutorials/B2A101-Y4SEventGeneration.dec',
find_file_ignore_error)
# In current main branch and release 5 the Y(4S)decay file is moved,
# so try old and new locations.

# With ``silent=True``, ``find_file`` returns empty string if nothing is
# found. With ``silent=False``, a ``FileNotFoundError`` exception is
# raised.
dec_file = basf2.find_file(
'analysis/examples/tutorials/B2A101-Y4SEventGeneration.dec', silent=True
)
if not dec_file:
dec_file = Belle2.FileSystem.findFile('analysis/examples/simulations/B2A101-Y4SEventGeneration.dec')
dec_file = basf2.find_file('analysis/examples/simulations/B2A101-Y4SEventGeneration.dec')
elif self.event_type == SimulationType.continuum:
dec_file = Belle2.FileSystem.findFile('analysis/examples/simulations/B2A102-ccbarEventGeneration.dec')
dec_file = basf2.find_file('analysis/examples/simulations/B2A102-ccbarEventGeneration.dec')
else:
raise ValueError(f"Event type {self.event_type} is not valid. It should be either 'Y(4S)' or 'Continuum'!")

generators.add_evtgen_generator(path, 'signal', dec_file)
modularAnalysis.loadGearbox(path)
simulation.add_simulation(path)

path.add_module('RootOutput', outputFileName=self.get_output_file_name('simulation_full_output.root'))
Expand All @@ -56,10 +60,12 @@ def create_path(self):
path = basf2.create_path()

path.add_module('RootInput', inputFileNames=self.get_input_file_names("simulation_full_output.root"))
modularAnalysis.loadGearbox(path)

path.add_module('Gearbox')
path.add_module('Geometry')
reconstruction.add_reconstruction(path)

modularAnalysis.outputMdst(self.get_output_file_name("reconstructed_output.root"), path=path)
mdst.add_mdst_output(path=path, filename=self.get_output_file_name("reconstructed_output.root"))

return path

Expand All @@ -70,7 +76,7 @@ def output(self):
@luigi.requires(ReconstructionTask)
class AnalysisTask(Basf2PathTask):
def create_path(self):
path = basf2.create_path()
path = basf2.Path()
modularAnalysis.inputMdstList('default', self.get_input_file_names("reconstructed_output.root"), path=path)
modularAnalysis.fillParticleLists([('K+', 'kaonID > 0.1'), ('pi+', 'pionID > 0.1')], path=path)
modularAnalysis.reconstructDecay('D0 -> K- pi+', '1.7 < M < 1.9', path=path)
Expand Down
3 changes: 1 addition & 2 deletions examples/gbasf2/example_mdst_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"""

import basf2
import ROOT # noqa: F401
import modularAnalysis as mA
import vertex as vx
from stdCharged import stdK, stdPi
Expand All @@ -22,7 +21,7 @@ def create_analysis_path(
parameter, adapted from code in the ``B2T_Basics_3_FirstAnalysis.ipynb``
notebook from b2 starter kit.
"""
path = basf2.create_path()
path = basf2.Path()
# this local inputMdstList will only be used when this steerig file is run locally, gbasf2 overrides it
local_input_files = ["/group/belle2/dataprod/MC/MC13a/prod00009434/s00/e1003/4S/r00000/mixed/mdst/sub00/mdst_000001_prod00009434_task10020000001.root"]
mA.inputMdstList(
Expand Down
Empty file added tests/basf2_helper/__init__.py
Empty file.
56 changes: 56 additions & 0 deletions tests/basf2_helper/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import sys
import warnings

from unittest import TestCase
from unittest.mock import patch

from b2luigi.basf2_helper.utils import get_basf2_git_hash


class TestGetBasf2GitHash(TestCase):
def test_return_no_set(self):
"""
On CI systems no basf2 is installed or set up, so ``get_basf2_git_hash``
should return \"not set\".
"""
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=ImportWarning)
self.assertEqual(get_basf2_git_hash(), "not_set")

def test_return_no_set_warning(self):
"""
On CI systems no basf2 is installed or set up, so ``get_basf2_git_hash``
should print an ``ImportWarning``.
"""
with self.assertWarns(ImportWarning):
get_basf2_git_hash()

@patch.dict(os.environ, {"BELLE2_RELEASE": "belle2_release_xy"})
def test_belle2_release_env_is_set(self):
self.assertEqual(get_basf2_git_hash(), "belle2_release_xy")

def test_basf2_hash_from_get_version(self):
"In older basf2 releases we get version from ``basf2.version.get_version``."
class MockVersion:
@staticmethod
def get_version():
return "some_basf2_version"

class MockBasf2:
version = MockVersion

with patch.dict(sys.modules, {"basf2": MockBasf2, "basf2.version": MockVersion}):
self.assertEqual(get_basf2_git_hash(), "some_basf2_version")

def test_basf2_hash_from_version_property(self):
"In newer basf2 releases we get version from ``basf2.version.version``."

class MockVersion:
version = "another_basf2_version"

class MockBasf2:
version = MockVersion

with patch.dict(sys.modules, {"basf2": MockBasf2, "basf2.version": MockVersion}):
self.assertEqual(get_basf2_git_hash(), "another_basf2_version")