There are many extension points that you can use to add capabilities to the translation points. All of them are used by LINQToTTree itself, so you can find plenty of example code. This page lists some of them. Some more advanced knowledge about .NET and the C++ world and ROOT is required to use these methods. Some extension methods are simple, some are… hard and require a lot of work.

Simple Function Mapping

This method works when you have a simple function in C++ that you’d like to use from your LINQ queries. The best example of this can be found in the ConfigData of a project you have inserted the nuget LINTToROOT package into. The file format should be pretty obvious – the include line is used to specify what files to include in order to get the referenced functions working. The rest of the translation is… well simple:

include: cmath
Math Sin(System.Double) => std::sin(double)
Math Cos(System.Double) => std::cos(double)
Math Tan(System.Double) => std::tan(double)

include: algorithm
Math Max(System.Double, System.Double) => std::max(double, double)
Math Max(System.Single, System.Single) => std::max(float, float)
Math Max(System.Int32, System.Int32) => std::max(int, int)

Create a new file in the ConfigData folder that has a filetype of .classmethodmappings and the infrastructure should find it automatically.

This method is very easy.

Adding C++ inline Code

If you have several lines of code you’d like to add in the middle of a query you can use the CPPCode attribute. For example, if you want to create a TLorentzVector so you can do easy DeltaR comparisons you need some extra code. Here is a sample:

        [CPPCode(IncludeFiles = new string[] { "TLorentzVector.h" },
            Code = new string[]{
                "TLorentzVector tlzUnique;",
                "tlzUnique.SetPtEtaPhiE(pt, eta, phi, E);",
                "CreateTLZ = &tlzUnique;"
        public static ROOTNET.NTLorentzVector CreateTLZ(double pt, double eta, double phi, double E)
            throw new NotImplementedException("This should never get called!");
#if false
            var tlz = new ROOTNET.NTLorentzVector();
            tlz.SetPtEtaPhiE(pt, eta, phi, E);
            return tlz;
This is taken from the LINQToTTreeHelpers.ROOTUtiles source file.

The attribute starts with a list of include files. The are pumped directly into C++ #include <filename> statements. After that is a list of lines for the actual C++ code. A few things:

  • Argument names are taken from the actual method definition – so make sure they are the same!!
  • The “Unique” name in a variable name is replaced by some unique string. Use this if you need to create a temporary variable. In this case it is required so that one can create two TLorentzVectors in the same query (so you can do some DeltaR comparisons!).
  • Return is not done with a return statement, but rather by setting the function name equal to the thing you are returning.
  • Objects that LINQ uses are always pointers! So if you are creating an object, as shown here, you must return a pointer.
  • Do not use new!! LINQ has no proviso to call a clean up routine when it is done! Use scoping rules to make sure things are cleaned up when you are done! The above example does just that.
  • If you make a syntax error in the C++ section you’ll only see it when the query is built. You’ll get a syntax error from the C++ compile setup when the query is made. Since the query will fail, the source C++ file should be left on your disk (see error message to find it) and you can look there to figure out what you did wrong.The code inside the C# method is never ever called by the LINQ infrastructure. However, I write the code anyway to remind myself of what the C++ is supposed to be doing in the .NET world.
  • Doing the query in LINQ is just like using the CreateTLZ function.

Example using this in a query:

            var tagAndProbeAssociated = from evt in goodTPJets
                                        let pJet = evt.ProbeJet
                                        let ptlz = ROOTUtils.CreateTLZ(pJet.Pt, pJet.Eta, pJet.Phi, pJet.E)
                                        select new EventHolder()
                                            Event = evt.Event,
                                            TagJet = evt.TagJet,
                                            ProbeJet = evt.ProbeJet,
                                            Tracks = from t in evt.Event.Tracks.AsQueryable().Where(goodTrackCuts.GetSelectedItemsExpression())
                                                     let ttlz = ROOTUtils.CreateTLZ(t.Pt, t.Eta, t.Phi)
                                                     where ptlz.DeltaR2(ttlz) < JetTrackDR2
                                                     select t,
                                            CalRatioTriggers = from cratio in evt.Event.CalRatioTrigger.AsQueryable()
                                                               let ctlx = ROOTUtils.CreateTLZ(cratio.ET, cratio.Eta, cratio.Phi)
                                                               where ctlx.DeltaR2(ptlz) < JetTriggerMatchDR2
                                                               select cratio,
                                            TracklessJetTriggers = from trackless in evt.Event.TracklessJetTrigger.AsQueryable()
                                                                   let ttlx = ROOTUtils.CreateTLZ(trackless.ET, trackless.Eta, trackless.Phi)
                                                                   where ttlx.DeltaR2(ptlz) < JetTriggerMatchDR2
                                                                   select trackless

Despite these caveats, this is actually a very easy way to inject some C++ code directly into your queries.

This method of extension is easy.

A New Datatype

Moving data between the .NET world and the C++ world requires translation. Especially because sometimes you want to move things across the wire for a PROOF query (not implemented as of 0.41, but coming). As a result, LINTToROOT needs to know about each data type it will move across the wire.

This implementation is a little more difficult and requires that you implement the interface ITypeHandler. Not everything has to be implemented, depending on how the object is to be written. Look for implementations of this in the source code to see how it is used. When I get an add-on data type that isn’t part of the actual library I’ll write up a better example here.

This method of extension is hard.

A New Result Type

Currently integer, double, and TObject’s are all supported as possible query results. That doesn’t mean you can’t create another result type if you want. However, in order to do this you’ll have to implement the IVariableSaver interface. This is because the system caches all results, and this interface is used to guide this process.

This method of extension is hard.

A New LINQ Predicate

The library comes with a number of custom predicates, like UniqueCombination. The nice thing about doing the implementation this way is that you can implement a custom translation to C++ code (these predicates generate C++ code). However, there are a lot of requirements and restrictions on this code. For example, the generated code has to know how to combine itself with other similar blocks of code! There are plenty of implementations. See the ResultOperators folder in the LINQToTTreeLib project.

This method of extension is very hard.

A New Execution Engine

What? Running locally isn’t enough? You want to also run in batch mode or some sort of new and improved PROOF like thing? In that case you need to only implement the interface called IQueryExecutor. Two methods. See the LINQToTTReeLib’s ExecutionCommon folder for an example.

This method of extension is very hard.

Last edited Oct 11, 2011 at 11:48 AM by gwatts, version 3


No comments yet.