Cannot perform both dot product and scalar multiplication on TVector2 in PyROOT

I think, because of the way the Python bindings for ROOT are created, that __mul__ can only be linked to one of the overloaded operator* functions in TVector2, namely, the first one that is called in the script.

Therefore, both of these scripts fail on Line 3 with could not convert argument 2:

import ROOT
v1 = ROOT.TVector2(1, 1) * ROOT.TVector2(2, 2) # this works
v2 = ROOT.TVector2(1, 1) * 5                   # this doesn't
import ROOT
v1 = ROOT.TVector2(1, 1) * 5                   # this works
v2 = ROOT.TVector2(1, 1) * ROOT.TVector2(2, 2) # this doesn't

Is there an obvious way to force the TVector2 to use one version of operator* vs. another, or do I have to use TVector3 with a zero for the z component, or implement either the scalar multiplication or dot product manually myself, whichever comes second?

Note: This isn’t a problem for TVector3 because the set of methods are completely different, for some reason: TVector3::Mag() is TVector2::Mod() and TVector3 defines a TVector3::Dot(TVector3), while TVector2 overloads the * for both scalar multiplication and the dot product. The Pythonic way of overloading __mul__ is to “duck type”: assume the passed argument is the right type, try something, then handle the exception:

class TVector2(object)
...
    def __mul__(self, second):
        try:
            return self.X()*second.X() + self.Y()*second.Y()
        except:
            return TVector2(self.X()*second, self.Y()*second)

(and similar code for __rmul__)
… but I guess this was difficult to do with compiled code or with automated Python bindings.

PyROOT sets up slots to do lookups of number-like functions (such as mul) lazily. The reason is that C++ allows both in-class, in-namespace, and global overloads (and sometimes in compiler-specific private namespaces). Searching all of that for every class just in case is expensive (not to mention error-prone, as the functions can live in different libraries, or even be template instantiations, thus lookup failure now does not mean lookup failure later). Instead there are stubs that lookup and replace themselves when called the first time.

Problem is, that lookup finds a specific global overload and uses that from there on out. It should continue to do lookups if known overloads fail, and add those new overloads as appropriate.

Not sure about a clear workaround, but you can force the issue:

def fixtv2mul():
   ROOT.TVector2(1, 1) * ROOT.TVector2(2, 2)
   mvv = ROOT.TVector2.__mul__
   del ROOT.TVector2.__mul__
   ROOT.TVector2(1, 1) * 5
   mvs = ROOT.TVector2.__mul__
   def fixmul(self, other, mvv=mvv, mvs=mvs):
      if isinstance(other, self.__class__):
         return mvv(self, other)
      return mvs(self, other)
   ROOT.TVector2.__mul__ = fixmul

fixtv2mul()

and down from there, all should be okay.

Thanks!

I already have some classes derived from the TVector classes, so I think I’ll just implement the functionality in them.

It’s good to know a little more about how the lookups and overloading works.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.