Software for Liquid Argon time projection chambers
This is a working set of rules and guidelines. Suggestions for improvements are welcomed. Basically, we urge lean, modular code that is detector-agnostic. LArSoft code is intended to be usable by any N-plane, N_w wires, M-PMT Liquid Argon TPC.
histos.root
should be generalizable for N wire planes and / or M PMTs. Obviously, the number of wires N_w per plane must be generalizable.
GetCollectionPlane()
and GetInductionPlane()
as methods of the class to return, for example, the first and last wire plane). One can test whether a plane is induction or collection using the Geometry interface already, geo::PlaneGeo::SignalType()
, so if you have a plane number you can ask the Geometry if it is induction or collection.All LArSoft users are expected to follow the guidelines for developing configurations and configuration-aware C code as described at https://larsoft.org/configuration/ which includes two presentations, one with video.
Conventions to keep in mind when writing code for LArSoft include the following:
using namespace XXX
statements are allowed.evd
for the EventDisplay package.f
, i.e., fADC
.i
is ok in a small for loop, not ok in one spanning 20 lines or more.typedef int Int_t
, typedef std::vector<double> dubvec
. Typedefs should be reserved for legitimate new types, i.e., Origin_t
in SimulationBase/MCTruth.h
.MyAlgo
should be written into MyAlgo.h
and MyAlgo.cxx
.MyModule
should be declared, defined, and implemented in MyModule_module.cc
.#include "larcore/Geometry/GeometryCore.h"
).LArSoft is considering adopting clang-format
as a code-formatting tool to ensure a uniform code layout. Although clang-format
can be made available by explicitly setting up the clang
UPS product, the recommended way of using clang-format
is to integrate it into your editor.
To use the UPS-provided clang-format
utility inside the editors, it is necessary that UPS is setup for your file-editing session. If UPS has been setup, then you should see the following when typed in your shell:
type ups >& /dev/null && echo UPS available
UPS available
If UPS is not found or Clang 7 is not installed in the UPS area, then you will not have access to the clang-format
commands within the editors.
Download attachment:ups-claing-format-v7.el and place it in a directory <my_dir>
.
Add the following commands to your .emacs file:
(add-to-list 'load-path "<my_dir>")
(load "ups-clang-format-v7")
This will allow you to use the 'M-x clang-format-buffer'
and 'M-x clang-format-region'
commands while editing files.
~/.vim/plugin/
subdirectory.'Ctrl-k'
while editing files to apply clang-format
to the selected line or region.For the files being edited, the clang-format
commands will use the .clang-format
file that is closest to the file. In other words, assume a file has the full path:
/dir1/dir2/dir3/my_file.cpp
and that each of the directories dir1
, dir2
, and dir3
have .clang-format
files. clang-format
will choose the .clang-format
file located in dir3
as the style to apply to the code in my_file.cpp
. If dir3
does not have a .clang-format
file, then clang-format
will search for a .clang-format
file in dir2
, and so on. If a .clang-format
file cannot be found in the directory hierarchy, then clang-format
applies its own default.
The problem of conflicting names can arise when developing code against multiple experiment repositories (e.g., argoneutcode
, lariatsoft
, uboonecode
, dunetpc
, etc.). In order to facilitate being able to develop in a multiple experiment environment, it is helpful if the experiments use unique naming for classes, headers, libraries, and fcl files.
#include "uboone/OpticalDetectorSim/RandomServer.h" // uboonecode
#include "dune/Geometry/ChannelMap35Alg.h" // dunetpc
#include "MCShowerReco/MCShower.h" // argoneutcode
uboone
, dune
, argoevd
) may be used to create a unique name, but it is safer to rely on the name of the class itself.libart_Persistency_Common.so
rather than just libCommon.so
).
art_make()
will do this by defaultBASENAME_ONLY
with art_make()
is not allowed in LArSoft. We strongly recommend experiment code follow the same practice.USE_PRODUCT_NAME
is specified, the UPS product name (e.g., dunetpc
) will be prepended to the calculated library nameversion
. Instead use mypackage_version
.The ARTists have provided a nice set of “good practices” for coding that also include some hints on newer C standard (2011, 2014…). Please read this document.
CMS has a very useful set of notes about Using Data Structures Safely with Threads.
// For each plane, do something on the hits on that plane
std::vector<recob::Hit> const& hits = *hitHandle;
auto const* geom = lar::providerFrom<geo::Geometry>();
// iterates through all planes in the detector (all TPCs!)
for (geo::PlaneID const& pid: geom->IteratePlaneIDs()) {
std::vector<recob::Hit const*> hitsOnPlane;
for (recob::Hit const& hit: hits) {
if (hit.WireID().planeID() != pid) continue;
hitsOnPlane.push_back(&hit);
} // for hits
geo::View_t view = geom->View(pid); // this is the view in this plane
// do something
} // for planes
Transient data is data that is specific to a given event but it is not saved into the event record. For example, results of an algorithm that need to be translated into LArSoft data products are transient.
Transient data should be local, and even if conditions force it to be a data member of a class, they should be emptied at the end of each event rather than at the beginning. Otherwise, you are carrying your now useless transient data across the whole job, wasting memory.
Let’s assume we have an algorithm whose workflow is split in different phases: initialisation, run and result retrieval.
class MyProducer: public art::EDProducer {
std::unique_ptr<MyAlgorithm> myAlgo;
// ...
};
void MyProducer::produce(art::Event& event) {
// configure the algorithm, provide it inputs...
// run the algorithm
myAlgo->run();
// retrieve the results (may be more than just one collection)
std::vector<recob::Vertex> vertices;
myAlgo->getVertices(vertices);
// move the results into the event
event.put(std::make_unique<std::vector<recob::Vertex>>(std::move(vertices)));
} // MyProducer::produce()
This requires the algorithm to keep the results until they are retrieved.
To implement correctly the algorithm, there are two main options, plus a “backup” one:
class MyAlgorithm {
public:
struct Results_t {
std::vector<recob::Vertex> vertices; ///< result: vertices
// ...
};
results_t run() const
{
Results_t results;
// fill the results by executing the algorithm code
return results;
}
//...
} // class MyAlgorithm
with a produce()
like:
void MyProducer::produce(art::Event& event) {
// configure the algorithm, provide it inputs...
// run the algorithm
auto results = myAlgo->run();
// move the results into the event
event.put
(std::make_unique<std::vector<recob::Vertex>>(std::move(results.vertices)));
} // MyProducer::produce()
If there is only one result, the Results_t
data structure is optional.
This solution allows avoiding copies of the result(s).
The following variant keeps the two phases separate:
class MyAlgorithm {
public:
struct Results_t {
std::vector<recob::Vertex> vertices; ///< result: vertices
// ...
};
void run();
Results_t getResults() { return std::move(results); }
//...
private:
Results_t results;
} // class MyAlgorithm
This still presents memory hoarding if the caller decides not to getResults()
, and it gives wrong results if getResults()
is called more than once. For these reasons, it is disfavoured respect to the first variant.
class MyAlgorithm {
std::vector<recob::Vertex> vertices; ///< result: vertices
public:
std::vector<recob::Vertex> const& getVertices() const
{ return vertices; }
void clear()
{ vertices.clear(); /* ... */ }
// ...
} // class MyAlgorithm
paired with a different version of produce()
:
void MyProducer::produce(art::Event& event) {
// configure the algorithm, provide it inputs...
// run the algorithm
myAlgo->run();
// retrieve the results (may be more than just one collection)
std::vector<recob::Vertex> const& vertices = myAlgo->getVertices();
// move the results into the event
event.put(std::make_unique<std::vector<recob::Vertex>>(vertices));
myAlgo->clear();
} // MyProducer::produce()
The main weakness of this approach is that it can’t verify that the caller actually clear()
ed the data, and it is therefore not recommended.
Note that this will not avoid a copy of the vertices, since the art producer will be forced to create a copy to be entrusted to the event.
void MyProducer::produce(art::Event& event) {
// construct and configure the algorithm, provide it inputs...
MyAlgorithm myAlgo;
// run the algorithm, do whatever it takes to get the results out of it
myAlgo.run();
// retrieve the results (may be more than just one collection)
std::vector<recob::Vertex> vertices;
myAlgo.getVertices(vertices);
// move the results into the event
event.put(std::make_unique<std::vector<recob::Vertex>>(std::move(vertices)));
} // MyProducer::produce()
</pre>
Example of **wrong** behavior:
```cpp
void MyProducer::produce(art::Event& event) {
// configure the algorithm, provide it inputs...
// run the algorithm
myAlgo->run();
// retrieve the results (may be more than just one collection)
std::vector<recob::Vertex> const& vertices = myAlgo->getVertices();
// move the results into the event
event.put(std::make_unique<std::vector<recob::Vertex>>(vertices));
} // MyProducer::produce()
This is the wrong implementation of the option 2, where we do not instruct the algorithm to clear its results, even if it is very likely that the algorithm is still retaining a copy of them.
auto const* geom = lar::providerFrom<geo::Geometry>();
if (geom->DetectorName() == "ArgoNeuT") {
// Do the desired specific ArgoNeuT thing here to get detector constants, e.g.
}
To get configuration for a specific detector, services should be used that are configured for that detector.
// Oi!
double plane_pitch = 0.3; //wire plane pitch in cm
double wire_pitch = 0.3; //wire pitch in cm
double Efield_SI = 0.8; // Electric Field between Shield and Induction planes in kV/cm
double Efield_IC = 0.65; // Electric Field between Induction and Collection
This is also bad (and at the end of this bad example we show a more correct approach): ```cpp
// This first bit is fine ....
auto const channel = theWire->Channel();
auto const* geom = lar::providerFrom<geo::Geometry>();
auto wireIDs = geom->ChannelToWire(channel); // note that there might be more wires read by the same readout channel
auto const& wireID = wireIDs->at(0);
// ... But here, we go off the rails.
//correct for the distance between wire planes
if (wireID.Plane == 0) time -= tI; // Induction
else if (wireID.Plane == 1) time -= (tI+tC); // Collection
// Bit below is especially odious ...
switch (wireID.Plane) {
case 0:
IclusHitlists.push_back(hitlist);
break;
case 1:
CclusHitlists.push_back(hitlist);
break;
} // switch
This is how this snippet should look like instead:
```cpp
auto const* geom = lar::providerFrom<geo::Geometry>();
auto const signalType = geom->SignalType(theWire->Channel());
switch (signalType) {
case geo::kInduction:
time -= tI;
IclusHitlists.push_back(hitlist);
break;
case geo::kCollection:
time -= (tI+tC);
CclusHitlists.push_back(hitlist);
break;
default:
throw art::Exception(art::errors::LogicError) << "Unexpected signal type!\n";
} // switch
That is: use the right abstraction (here, signal type and channel, instead of wire and plane number), and rely on the geometry service to map readout and geometry.
We value your contributions. We are all physicists and not computer scientists. We want everyone to be able to walk in and contribute to LArSoft. Nevertheless, your LArSoft co-conveners have decided some words are in order regarding coding conventions. We run unit tests, and experiments can run regression tests (through the Continuous Integration), and we want more. No one has volunteered to spend the many hours necessary to vet the code of contributors and make it conform to the proper rules and aesthetics. Our use of Code Analysis and spot checks and urging and individual follow-up will continue as much as time allows.
Mechanism for enforcement of violators is loose. The most serious route toward urging re-examining of your code that may be in violation of the tenants listed is through a discussion with the appropriate experiment spokesperson. Only eventually, in extreme cases and as a last resort, will repeat offenders of the codified rules be locked out of the repository. But, it could happen. Please do your best to follow the rules. See example code snippets on this page and code throughout the repository to guide you.