go-hep.org/x/hep@v0.38.1/fastjet/clustersequence.go (about)

     1  // Copyright ©2017 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fastjet
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"math"
    11  
    12  	"go-hep.org/x/hep/fmom"
    13  )
    14  
    15  // history holds information about the clustering
    16  type history struct {
    17  	parent1 int // index of first parent of this jet were created
    18  	parent2 int // index of second parent of this jet were created
    19  	child   int // index where the current jet is recombined with another
    20  	jet     int
    21  	dij     float64
    22  	maxdij  float64
    23  }
    24  
    25  const (
    26  	invalidIndex     = -3
    27  	inexistentParent = -2
    28  	beamJetIndex     = -1
    29  )
    30  
    31  type ClusterSequence struct {
    32  	def      JetDefinition
    33  	alg      JetAlgorithm
    34  	strategy Strategy
    35  	r        float64
    36  	r2       float64
    37  	invR2    float64
    38  	qtot     float64
    39  	initn    int
    40  
    41  	jets      []Jet
    42  	history   []history
    43  	structure JetStructure
    44  }
    45  
    46  func NewClusterSequence(jets []Jet, def JetDefinition) (*ClusterSequence, error) {
    47  	var err error
    48  	cs := &ClusterSequence{
    49  		def:      def,
    50  		alg:      def.Algorithm(),
    51  		strategy: def.Strategy(),
    52  		r:        def.R(),
    53  		jets:     make([]Jet, len(jets), len(jets)*2),
    54  	}
    55  
    56  	cs.r2 = cs.r * cs.r
    57  	cs.invR2 = 1.0 / cs.r2
    58  	cs.structure = ClusterSequenceStructure{cs}
    59  
    60  	copy(cs.jets, jets)
    61  	err = cs.init()
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	err = cs.run()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	return cs, err
    72  }
    73  
    74  // NumExclusiveJets returns the number of exclusive jets that would have been obtained
    75  // running the algorithm in exclusive mode with the given dcut
    76  func (cs *ClusterSequence) NumExclusiveJets(dcut float64) int {
    77  	// first locate where clustering would have stopped
    78  	i := len(cs.history) - 1 // last jet
    79  	for ; 0 <= i; i-- {
    80  		if cs.history[i].maxdij <= dcut {
    81  			break
    82  		}
    83  	}
    84  	stoppt := i + 1
    85  	njets := 2*cs.initn - stoppt
    86  	return njets
    87  }
    88  
    89  func (cs *ClusterSequence) ExclusiveJets(dcut float64) ([]Jet, error) {
    90  	njets := cs.NumExclusiveJets(dcut)
    91  	return cs.ExclusiveJetsUpTo(njets)
    92  }
    93  
    94  func (cs *ClusterSequence) ExclusiveJetsUpTo(njets int) ([]Jet, error) {
    95  	var err error
    96  	if njets > cs.initn {
    97  		err = errors.New("fastjet: requested too many exclusive jets")
    98  		return nil, err
    99  	}
   100  	// calculate the point where we have to stop the clustering
   101  	// relation between stoppt, njets assumes one extra jet disappears
   102  	// at each clustering
   103  	//
   104  	// make sure it's safe when more jets are requested than there are particles
   105  	stoppt := max(2*cs.initn-njets, cs.initn)
   106  	// additional checking
   107  	if 2*cs.initn != len(cs.history) {
   108  		err = errors.New("fastjet: too few initial jets")
   109  		return nil, err
   110  	}
   111  
   112  	// now go forwards and reconstitute the jets that we have --
   113  	// basically for any history element, see if the parent jets to
   114  	// which it refers were created before the stopping point -- if they
   115  	// were then add them to the list, otherwise they are subsequent
   116  	// recombinations of the jets that we are looking for
   117  	ljets := make([]Jet, 0, imin(njets, cs.initn))
   118  	for i := stoppt; i < len(cs.history); i++ {
   119  		parent1 := cs.history[i].parent1
   120  		if parent1 < stoppt {
   121  			ljets = append(ljets, cs.jets[cs.history[parent1].jet])
   122  		}
   123  		parent2 := cs.history[i].parent2
   124  		if 0 < parent2 && parent2 < stoppt {
   125  			ljets = append(ljets, cs.jets[cs.history[parent2].jet])
   126  		}
   127  	}
   128  	return ljets, err
   129  }
   130  
   131  func (cs *ClusterSequence) InclusiveJets(ptmin float64) ([]Jet, error) {
   132  	var err error
   133  	dcut := ptmin * ptmin
   134  	jets := make([]Jet, 0)
   135  	i := len(cs.history) - 1 // last jet
   136  
   137  	switch cs.alg {
   138  	case KtAlgorithm:
   139  		for ; 0 <= i; i-- {
   140  			// with our specific definition of dij and dib (ie: R appears only in
   141  			// dij) then dij==dib is the same as the jet.Pt2() and we can exploit
   142  			// this in selecting the jets...
   143  			if cs.history[i].maxdij < dcut {
   144  				break
   145  			}
   146  			if hh := cs.history[i]; hh.parent2 == beamJetIndex && hh.dij >= dcut {
   147  				// for beam jets
   148  				jets = append(jets, cs.jets[cs.history[hh.parent1].jet])
   149  			}
   150  		}
   151  	case CambridgeAlgorithm:
   152  		for ; 0 <= i; i-- {
   153  			// inclusive jets are all at the end of clustering sequence in the
   154  			// cambridge algorithm.
   155  			// if we find a non-exclusive jet, exit.
   156  			if cs.history[i].parent2 != beamJetIndex {
   157  				break
   158  			}
   159  			parent1 := cs.history[i].parent1
   160  			jet := &cs.jets[cs.history[parent1].jet]
   161  			if jet.Pt2() >= dcut {
   162  				jets = append(jets, *jet)
   163  			}
   164  		}
   165  
   166  	case PluginAlgorithm, EeKtAlgorithm, AntiKtAlgorithm,
   167  		GenKtAlgorithm, EeGenKtAlgorithm, CambridgeForPassiveAlgorithm:
   168  		// for inclusive jets with a plugin algorithm, we make no
   169  		// assumption about anything (relation of dij to momenta,
   170  		// ordering of the dij, etc...)
   171  		for ; 0 <= i; i-- {
   172  			hh := cs.history[i]
   173  			if hh.parent2 != beamJetIndex {
   174  				continue
   175  			}
   176  			parent1 := hh.parent1
   177  			jet := &cs.jets[cs.history[parent1].jet]
   178  			if jet.Pt2() >= dcut {
   179  				jets = append(jets, *jet)
   180  			}
   181  		}
   182  	}
   183  	return jets, err
   184  }
   185  
   186  func (cs *ClusterSequence) init() error {
   187  	var err error
   188  	cs.history = make([]history, 0, len(cs.jets)*2)
   189  	cs.qtot = 0
   190  
   191  	for i := range cs.jets {
   192  		jet := &cs.jets[i]
   193  		cs.history = append(cs.history,
   194  			history{
   195  				parent1: inexistentParent,
   196  				parent2: inexistentParent,
   197  				child:   invalidIndex,
   198  				jet:     i,
   199  				dij:     0.0,
   200  				maxdij:  0.0,
   201  			},
   202  		)
   203  		// perform any momentum pre-processing needed by the recombination scheme
   204  		err = cs.def.Recombiner().Preprocess(jet)
   205  		if err != nil {
   206  			return err
   207  		}
   208  
   209  		jet.hidx = i
   210  		jet.structure = cs.structure
   211  
   212  		cs.qtot += jet.E()
   213  	}
   214  	// store the initial number of particles.
   215  	// this is used by the ClusterSequence to compute the number of exclusive jets
   216  	// (in the ExclusiveJets method).
   217  	cs.initn = len(cs.jets)
   218  	return err
   219  }
   220  
   221  func (cs *ClusterSequence) run() error {
   222  	// nothing to run when event is empty
   223  	if len(cs.jets) <= 0 {
   224  		return nil
   225  	}
   226  
   227  	run := cs.runN3Dumb
   228  
   229  	switch cs.strategy {
   230  	case NlnNStrategy:
   231  		run = cs.runNlnN
   232  	case N3DumbStrategy:
   233  		run = cs.runN3Dumb
   234  	}
   235  
   236  	err := run()
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  // Constituents retrieves the list of constituents of a given jet
   245  func (cs *ClusterSequence) Constituents(jet *Jet) ([]Jet, error) {
   246  	return cs.addConstituents(jet)
   247  }
   248  
   249  func (cs *ClusterSequence) addConstituents(jet *Jet) ([]Jet, error) {
   250  	var err error
   251  	var subjets []Jet
   252  
   253  	// find position in cluster history
   254  	i := jet.hidx
   255  	hh := &cs.history[i]
   256  	parent1 := hh.parent1
   257  	if parent1 == inexistentParent {
   258  		// It is an original particle (labelled by its parent having value
   259  		// inexistentParent), therefore add it on to the subjet vector
   260  		// Note: we add the initial particle and not simply 'jet' so that
   261  		//       calling addConstituents with a subtracted jet containing
   262  		//       only one particle will work.
   263  		subjets = append(subjets, cs.jets[i])
   264  		return subjets, err
   265  	}
   266  
   267  	// add parent 1
   268  	sub1, err := cs.addConstituents(&cs.jets[cs.history[parent1].jet])
   269  	if err != nil {
   270  		return subjets, err
   271  	}
   272  	subjets = append(subjets, sub1...)
   273  
   274  	// see if parent2 is a real jet, then add its constituents
   275  	parent2 := hh.parent2
   276  	if parent2 == beamJetIndex {
   277  		return subjets, err
   278  	}
   279  
   280  	sub2, err := cs.addConstituents(&cs.jets[cs.history[parent2].jet])
   281  	if err != nil {
   282  		return subjets, err
   283  	}
   284  	subjets = append(subjets, sub2...)
   285  
   286  	return subjets, err
   287  }
   288  
   289  func (cs *ClusterSequence) jetScaleForAlgorithm(jet *Jet) float64 {
   290  	switch cs.alg {
   291  
   292  	case KtAlgorithm:
   293  		return jet.Pt2()
   294  
   295  	case CambridgeAlgorithm:
   296  		return 1.0
   297  
   298  	case AntiKtAlgorithm:
   299  		kt2 := jet.Pt2()
   300  		if kt2 > 1e-300 {
   301  			return 1.0 / kt2
   302  		}
   303  		return 1e300
   304  
   305  	case GenKtAlgorithm:
   306  		kt2 := jet.Pt2()
   307  		p := cs.def.ExtraParam()
   308  		if p <= 0 && kt2 < 1e-300 {
   309  			kt2 = 1e-300
   310  		}
   311  		return math.Pow(kt2, p)
   312  
   313  	case CambridgeForPassiveAlgorithm:
   314  		kt2 := jet.Pt2()
   315  		lim := cs.def.ExtraParam()
   316  		if kt2 < lim*lim && kt2 != 0 {
   317  			return 1.0 / kt2
   318  		}
   319  		return 1.0
   320  
   321  	case EeGenKtAlgorithm:
   322  		kt2 := jet.E()
   323  		p := cs.def.ExtraParam()
   324  		if p <= 0 && kt2 < 1e-300 {
   325  			kt2 = 1e-300
   326  		}
   327  		return math.Pow(kt2, 2*p)
   328  
   329  	case EeKtAlgorithm:
   330  		e := max(jet.E(), 1e-300)
   331  		return e * e
   332  
   333  	default:
   334  		panic(fmt.Errorf("fastjet: unrecognised jet algorithm (%v)", cs.alg))
   335  	}
   336  }
   337  
   338  func (cs *ClusterSequence) setStructure(j *Jet) {
   339  	j.structure = cs.structure
   340  }
   341  
   342  // do_ij_recombination_step
   343  func (cs *ClusterSequence) ijRecombinationStep(i, j int, dij float64) (int, error) {
   344  
   345  	k := -1
   346  	// create the new jet by recombining the first two
   347  	ijet := &cs.jets[i]
   348  	jjet := &cs.jets[j]
   349  	kjet, err := cs.def.Recombiner().Recombine(ijet, jjet)
   350  	if err != nil {
   351  		return k, err
   352  	}
   353  	k = len(cs.jets)
   354  	khist := len(cs.history)
   355  	kjet.hidx = khist
   356  	cs.jets = append(cs.jets, kjet)
   357  
   358  	ihist := ijet.hidx
   359  	jhist := jjet.hidx
   360  
   361  	err = cs.addStepToHistory(khist, imin(ihist, jhist), imax(ihist, jhist), k, dij)
   362  	return k, err
   363  }
   364  
   365  func (cs *ClusterSequence) ibRecombinationStep(i int, dib float64) error {
   366  	k := len(cs.history)
   367  	err := cs.addStepToHistory(k, cs.jets[i].hidx, beamJetIndex, invalidIndex, dib)
   368  	return err
   369  }
   370  
   371  func (cs *ClusterSequence) addStepToHistory(istep, i1, i2, idx int, dij float64) error {
   372  	var err error
   373  
   374  	cs.history = append(cs.history,
   375  		history{
   376  			parent1: i1,
   377  			parent2: i2,
   378  			jet:     idx,
   379  			child:   invalidIndex,
   380  			dij:     dij,
   381  			maxdij:  math.Max(dij, cs.history[len(cs.history)-1].maxdij),
   382  		},
   383  	)
   384  	step := len(cs.history) - 1
   385  	if step != istep {
   386  		panic(fmt.Errorf("fastjet: internal logic error (step number dont match (%d != %d))",
   387  			step, istep,
   388  		))
   389  	}
   390  
   391  	cs.history[i1].child = step
   392  	if i2 >= 0 {
   393  		cs.history[i2].child = step
   394  	}
   395  
   396  	// get cross-referencing right
   397  	if idx != invalidIndex {
   398  		cs.jets[idx].hidx = step
   399  		cs.setStructure(&cs.jets[idx])
   400  	}
   401  
   402  	return err
   403  }
   404  
   405  // runs the N3Dumb strategy
   406  func (cs *ClusterSequence) runN3Dumb() error {
   407  	var err error
   408  	njets := len(cs.jets)
   409  	type jetinfo struct {
   410  		jet *Jet
   411  		idx int
   412  	}
   413  	jets := make([]jetinfo, njets)
   414  	indices := make([]int, njets)
   415  
   416  	for i := range cs.jets {
   417  		jets[i] = jetinfo{
   418  			jet: &cs.jets[i],
   419  			idx: i,
   420  		}
   421  		indices[i] = i
   422  	}
   423  
   424  	for n := njets; n > 0; n-- {
   425  		ii := 0
   426  		jj := -2
   427  		// find smallest beam distance
   428  		ymin := cs.jetScaleForAlgorithm(jets[0].jet)
   429  		for i := range n {
   430  			y := cs.jetScaleForAlgorithm(jets[i].jet)
   431  			if y < ymin {
   432  				ymin = y
   433  				ii = i
   434  				jj = -2
   435  			}
   436  		}
   437  
   438  		// find smallest distance between pair of jets
   439  		for i := range n - 1 {
   440  			ijet := jets[i].jet
   441  			for j := i + 1; j < n; j++ {
   442  				jjet := jets[j].jet
   443  				jetscale := math.Min(
   444  					cs.jetScaleForAlgorithm(ijet),
   445  					cs.jetScaleForAlgorithm(jjet),
   446  				)
   447  				y := math.MaxFloat64
   448  				switch cs.alg {
   449  				case EeGenKtAlgorithm:
   450  					den := 1 - math.Cos(cs.r)
   451  					if cs.r > math.Pi {
   452  						den = 3 + math.Cos(cs.r)
   453  					}
   454  					if den != 0 {
   455  						y = jetscale * (1 - fmom.CosTheta(&ijet.PxPyPzE, &jjet.PxPyPzE)) / den
   456  					}
   457  
   458  				case EeKtAlgorithm:
   459  					y = 2 * jetscale * (1 - fmom.CosTheta(&ijet.PxPyPzE, &jjet.PxPyPzE))
   460  
   461  				default:
   462  					y = jetscale * Distance(ijet, jjet) * cs.invR2
   463  				}
   464  				if y < ymin {
   465  					ymin = y
   466  					ii = i
   467  					jj = j
   468  				}
   469  			}
   470  		}
   471  
   472  		// now recombine
   473  		newn := 2*len(jets) - n
   474  		if jj >= 0 {
   475  			//combine pair
   476  			nn, err := cs.ijRecombinationStep(jets[ii].idx, jets[jj].idx, ymin)
   477  			if err != nil {
   478  				return err
   479  			}
   480  			// internal bookkeeping
   481  			jets[ii] = jetinfo{
   482  				jet: &cs.jets[nn],
   483  				idx: nn,
   484  			}
   485  			// have jj point to jet that was pointed at by n-1
   486  			// since original jj is no longer current
   487  			jets[jj] = jets[n-1]
   488  			indices[ii] = newn
   489  			indices[jj] = indices[n-1]
   490  		} else {
   491  			// combine ii with beam
   492  			err = cs.ibRecombinationStep(jets[ii].idx, ymin)
   493  			if err != nil {
   494  				return err
   495  			}
   496  			// put last jet in place of ii which has disappeared
   497  			jets[ii] = jets[n-1]
   498  			indices[ii] = indices[n-1]
   499  		}
   500  	}
   501  	return err
   502  }
   503  
   504  // runNlnN runs the clustering using a Hierarchical Delaunay triangulation
   505  // and a min-heap to achieve O(N*ln N) behaviour.
   506  //
   507  // There are internally asserted assumptions about absence of points
   508  // with coincident eta-phi coordinates.
   509  func (cs *ClusterSequence) runNlnN() error {
   510  	panic(fmt.Errorf("fastjet: runNlnN not implemented"))
   511  }
   512  
   513  // // addKtDistance adds the current kt distance for particle jeti to the heap
   514  // // using information about the nearest neighbor in the eta-phi plane.
   515  // // Work as follows:
   516  // //
   517  // // . if the kt is zero then its nearest neighbour is taken to be
   518  // //   the beam jet and the distance is zero.
   519  // //
   520  // // . if cylinder distance to nearest neighbour > r^2 then it is
   521  // //   yiB that is smallest and this is added to the heap.
   522  // //
   523  // // . otherwise if the nearest neighbour jj has a larger kt then add
   524  // //   dij to the heap.
   525  // //
   526  // // . otherwise do nothing
   527  // //
   528  // func (cs *ClusterSequence) addKtDistance(h *heap.Heap, jeti, jetj int, dist float64) {
   529  // 	yiB := cs.jetScaleForAlgorithm(&cs.jets[jeti])
   530  // 	if yiB == 0 {
   531  // 		h.Push(jeti, beamJetIndex, yiB)
   532  // 		return
   533  // 	}
   534  // 	deltaR2 := dist * cs.invR2
   535  // 	if deltaR2 > 1 {
   536  // 		h.Push(jeti, beamJetIndex, yiB)
   537  // 		return
   538  // 	}
   539  // 	if yiB <= cs.jetScaleForAlgorithm(&cs.jets[jetj]) {
   540  // 		dij := deltaR2 * yiB
   541  // 		h.Push(jeti, jetj, dij)
   542  // 	}
   543  // }