CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
GlobalTrajectoryBuilderBase.cc
Go to the documentation of this file.
1 
23 
24 //---------------
25 // C++ Headers --
26 //---------------
27 
28 #include <iostream>
29 #include <algorithm>
30 
31 //-------------------------------
32 // Collaborating Class Headers --
33 //-------------------------------
34 
38 
45 
47 
52 
57 
59 
66 
73 
76 
79 
80 using namespace std;
81 using namespace edm;
82 
83 //----------------
84 // Constructors --
85 //----------------
87  const MuonServiceProxy* service,
89  theTrackMatcher(0),theLayerMeasurements(0),theTrackTransformer(0),theRegionBuilder(0), theService(service),theGlbRefitter(0) {
90 
91  theCategory = par.getUntrackedParameter<string>("Category", "Muon|RecoMuon|GlobalMuon|GlobalTrajectoryBuilderBase");
92 
93 
94  ParameterSet trackMatcherPSet = par.getParameter<ParameterSet>("GlobalMuonTrackMatcher");
95  theTrackMatcher = new GlobalMuonTrackMatcher(trackMatcherPSet,theService);
96 
97  theTrackerPropagatorName = par.getParameter<string>("TrackerPropagator");
98 
99  ParameterSet trackTransformerPSet = par.getParameter<ParameterSet>("TrackTransformer");
100  theTrackTransformer = new TrackTransformer(trackTransformerPSet);
101 
102  ParameterSet regionBuilderPSet = par.getParameter<ParameterSet>("MuonTrackingRegionBuilder");
103 
104  theRegionBuilder = new MuonTrackingRegionBuilder(regionBuilderPSet,theService,iC);
105 
106  // TrackRefitter parameters
107  ParameterSet refitterParameters = par.getParameter<ParameterSet>("GlbRefitterParameters");
108  theGlbRefitter = new GlobalMuonRefitter(refitterParameters, theService, iC);
109 
110  theMuonHitsOption = refitterParameters.getParameter<int>("MuonHitsOption");
111 
112  theTrackerRecHitBuilderName = par.getParameter<string>("TrackerRecHitBuilder");
113  theMuonRecHitBuilderName = par.getParameter<string>("MuonRecHitBuilder");
114 
115  theRPCInTheFit = par.getParameter<bool>("RefitRPCHits");
116 
117  theTECxScale = par.getParameter<double>("ScaleTECxFactor");
118  theTECyScale = par.getParameter<double>("ScaleTECyFactor");
119  thePtCut = par.getParameter<double>("PtCut");
120  thePCut = par.getParameter<double>("PCut");
121 
122  theCacheId_TRH = 0;
123 
124 }
125 
126 
127 //--------------
128 // Destructor --
129 //--------------
131 
132  if (theTrackMatcher) delete theTrackMatcher;
135  if (theGlbRefitter) delete theGlbRefitter;
136 }
137 
138 
139 //
140 // set Event
141 //
143 
144  theEvent = &event;
145 
147  theRegionBuilder->setEvent(event);
148 
149  theGlbRefitter->setEvent(event);
150  theGlbRefitter->setServices(theService->eventSetup());
151 
152  unsigned long long newCacheId_TRH = theService->eventSetup().get<TransientRecHitRecord>().cacheIdentifier();
153  if ( newCacheId_TRH != theCacheId_TRH ) {
154  LogDebug(theCategory) << "TransientRecHitRecord changed!";
155  theCacheId_TRH = newCacheId_TRH;
158  }
159 
160  //Retrieve tracker topology from geometry
162  theService->eventSetup().get<IdealGeometryRecord>().get(tTopoHand);
163  tTopo_=tTopoHand.product();
164 
165 }
166 
167 
168 //
169 // build a combined tracker-muon trajectory
170 //
173  MuonCandidate::CandidateContainer& tkTrajs ) const {
174 
175  LogTrace(theCategory) << " Begin Build" << endl;
176 
177  // tracker trajectory should be built and refit before this point
178  if ( tkTrajs.empty() ) return CandidateContainer();
179 
180  // add muon hits and refit/smooth trajectories
181  CandidateContainer refittedResult;
182  ConstRecHitContainer muonRecHits = getTransientRecHits(*(staCand.second));
183 
184  // check order of muon measurements
185  if ( (muonRecHits.size() > 1) &&
186  ( muonRecHits.front()->globalPosition().mag() >
187  muonRecHits.back()->globalPosition().mag() ) ) {
188  LogTrace(theCategory)<< " reverse order: ";
189  }
190 
191  for ( CandidateContainer::const_iterator it = tkTrajs.begin(); it != tkTrajs.end(); it++ ) {
192 
193  // cut on tracks with low momenta
194  LogTrace(theCategory)<< " Track p and pT " << (*it)->trackerTrack()->p() << " " << (*it)->trackerTrack()->pt();
195  if( (*it)->trackerTrack()->p() < thePCut || (*it)->trackerTrack()->pt() < thePtCut ) continue;
196 
197  ConstRecHitContainer trackerRecHits;
198  if ((*it)->trackerTrack().isNonnull()) {
199  trackerRecHits = getTransientRecHits(*(*it)->trackerTrack());
200  } else {
201  LogDebug(theCategory)<<" NEED HITS FROM TRAJ";
202  //trackerRecHits = (*it)->trackerTrajectory()->recHits();
203  }
204 
205  // check for single TEC RecHits in trajectories in the overalp region
206  if ( fabs((*it)->trackerTrack()->eta()) > 0.95 && fabs((*it)->trackerTrack()->eta()) < 1.15 && (*it)->trackerTrack()->pt() < 60 ) {
207  if ( theTECxScale < 0 || theTECyScale < 0 )
208  trackerRecHits = selectTrackerHits(trackerRecHits);
209  else
210  fixTEC(trackerRecHits,theTECxScale,theTECyScale);
211  }
212 
213  RefitDirection recHitDir = checkRecHitsOrdering(trackerRecHits);
214  if ( recHitDir == outToIn ) reverse(trackerRecHits.begin(),trackerRecHits.end());
215 
216  reco::TransientTrack tTT((*it)->trackerTrack(),&*theService->magneticField(),theService->trackingGeometry());
217  TrajectoryStateOnSurface innerTsos = tTT.innermostMeasurementState();
218 
220  if((*it)->trackerTrack()->seedRef().isAvailable()) tmpSeed = (*it)->trackerTrack()->seedRef();
221 
222  if ( !innerTsos.isValid() ) {
223  LogTrace(theCategory) << " inner Trajectory State is invalid. ";
224  continue;
225  }
226 
227  innerTsos.rescaleError(100.);
228 
229  TC refitted0,refitted1;
230  MuonCandidate* finalTrajectory = 0;
231  Trajectory *tkTrajectory = 0;
232 
233  // tracker only track
234  if ( ! ((*it)->trackerTrajectory() && (*it)->trackerTrajectory()->isValid()) ) {
235  refitted0 = theTrackTransformer->transform((*it)->trackerTrack()) ;
236  if (!refitted0.empty()) tkTrajectory = new Trajectory(*(refitted0.begin()));
237  else LogWarning(theCategory)<< " Failed to load tracker track trajectory";
238  } else tkTrajectory = (*it)->trackerTrajectory();
239  if (tkTrajectory) tkTrajectory->setSeedRef(tmpSeed);
240 
241  // full track with all muon hits using theGlbRefitter
242  ConstRecHitContainer allRecHits = trackerRecHits;
243  allRecHits.insert(allRecHits.end(), muonRecHits.begin(),muonRecHits.end());
244  refitted1 = theGlbRefitter->refit( *(*it)->trackerTrack(), tTT, allRecHits,theMuonHitsOption, tTopo_);
245  LogTrace(theCategory)<<" This track-sta refitted to " << refitted1.size() << " trajectories";
246 
247  Trajectory *glbTrajectory1 = 0;
248  if (!refitted1.empty()) glbTrajectory1 = new Trajectory(*(refitted1.begin()));
249  else LogDebug(theCategory)<< " Failed to load global track trajectory 1";
250  if (glbTrajectory1) glbTrajectory1->setSeedRef(tmpSeed);
251 
252  finalTrajectory = 0;
253  if(glbTrajectory1 && tkTrajectory) finalTrajectory = new MuonCandidate(glbTrajectory1, (*it)->muonTrack(), (*it)->trackerTrack(),
254  tkTrajectory? new Trajectory(*tkTrajectory) : 0);
255 
256  if ( finalTrajectory )
257  refittedResult.push_back(finalTrajectory);
258 
259  if(tkTrajectory) delete tkTrajectory;
260  }
261 
262  // choose the best global fit for this Standalone Muon based on the track probability
263  CandidateContainer selectedResult;
264  MuonCandidate* tmpCand = 0;
265  if ( refittedResult.size() > 0 ) tmpCand = *(refittedResult.begin());
266  double minProb = 9999;
267 
268  for (CandidateContainer::const_iterator iter=refittedResult.begin(); iter != refittedResult.end(); iter++) {
269  double prob = trackProbability(*(*iter)->trajectory());
270  LogTrace(theCategory)<<" refitted-track-sta with pT " << (*iter)->trackerTrack()->pt() << " has probability " << prob;
271 
272  if (prob < minProb) {
273  minProb = prob;
274  tmpCand = (*iter);
275  }
276  }
277 
278  if ( tmpCand ) selectedResult.push_back(new MuonCandidate(new Trajectory(*(tmpCand->trajectory())), tmpCand->muonTrack(), tmpCand->trackerTrack(),
279  (tmpCand->trackerTrajectory())? new Trajectory( *(tmpCand->trackerTrajectory()) ):0 ) );
280 
281  for (CandidateContainer::const_iterator it = refittedResult.begin(); it != refittedResult.end(); ++it) {
282  if ( (*it)->trajectory() ) delete (*it)->trajectory();
283  if ( (*it)->trackerTrajectory() ) delete (*it)->trackerTrajectory();
284  if ( *it ) delete (*it);
285  }
286  refittedResult.clear();
287 
288  return selectedResult;
289 
290 }
291 
292 
293 //
294 // select tracker tracks within a region of interest
295 //
296 vector<GlobalTrajectoryBuilderBase::TrackCand>
298  const vector<TrackCand>& tkTs) {
299 
300  // define eta-phi region
301  RectangularEtaPhiTrackingRegion regionOfInterest = defineRegionOfInterest(staCand.second);
302 
303  // get region's etaRange and phiMargin
304  //UNUSED: PixelRecoRange<float> etaRange = regionOfInterest.etaRange();
305  //UNUSED: TkTrackingRegionsMargin<float> phiMargin = regionOfInterest.phiMargin();
306 
307  vector<TrackCand> result;
308 
309  double deltaR_max = 1.0;
310 
311  for ( vector<TrackCand>::const_iterator is = tkTs.begin(); is != tkTs.end(); ++is ) {
312  // check if each trackCand is in region of interest
313 // bool inEtaRange = etaRange.inside(is->second->eta());
314 // bool inPhiRange = (fabs(Geom::Phi<float>(is->second->phi()) - Geom::Phi<float>(regionOfInterest.direction().phi())) < phiMargin.right() ) ? true : false ;
315 
316  double deltaR_tmp = deltaR(static_cast<double>(regionOfInterest.direction().eta()),
317  static_cast<double>(regionOfInterest.direction().phi()),
318  is->second->eta(), is->second->phi());
319 
320  // for each trackCand in region, add trajectory and add to result
321  //if ( inEtaRange && inPhiRange ) {
322  if (deltaR_tmp < deltaR_max) {
323  TrackCand tmpCand = TrackCand(*is);
324  result.push_back(tmpCand);
325  }
326  }
327 
328  return result;
329 
330 }
331 
332 
333 //
334 // define a region of interest within the tracker
335 //
338 
340 
341  TkTrackingRegionsMargin<float> etaMargin(fabs(region1->etaRange().min() - region1->etaRange().mean()),
342  fabs(region1->etaRange().max() - region1->etaRange().mean()));
343 
344  RectangularEtaPhiTrackingRegion region2(region1->direction(),
345  region1->origin(),
346  region1->ptMin(),
347  region1->originRBound(),
348  region1->originZBound(),
349  etaMargin,
350  region1->phiMargin());
351 
352  delete region1;
353  return region2;
354 
355 }
356 
357 
358 //
359 // calculate the tail probability (-ln(P)) of a fit
360 //
361 double
363 
364  if ( track.ndof() > 0 && track.chiSquared() > 0 ) {
365  return -LnChiSquaredProbability(track.chiSquared(), track.ndof());
366  } else {
367  return 0.0;
368  }
369 
370 }
371 
372 
373 //
374 // print RecHits
375 //
377 
378  LogTrace(theCategory) << "Used RecHits: " << hits.size();
379  for (ConstRecHitContainer::const_iterator ir = hits.begin(); ir != hits.end(); ir++ ) {
380  if ( !(*ir)->isValid() ) {
381  LogTrace(theCategory) << "invalid RecHit";
382  continue;
383  }
384 
385  const GlobalPoint& pos = (*ir)->globalPosition();
386 
388  << "r = " << sqrt(pos.x() * pos.x() + pos.y() * pos.y())
389  << " z = " << pos.z()
390  << " dimension = " << (*ir)->dimension()
391  << " " << (*ir)->det()->geographicalId().det()
392  << " " << (*ir)->det()->subDetector();
393 
394  }
395 
396 }
397 
398 //
399 // check order of RechIts on a trajectory
400 //
403 
404  if ( !recHits.empty() ) {
405  ConstRecHitContainer::const_iterator frontHit = recHits.begin();
406  ConstRecHitContainer::const_iterator backHit = recHits.end() - 1;
407  while ( !(*frontHit)->isValid() && frontHit != backHit ) {frontHit++;}
408  while ( !(*backHit)->isValid() && backHit != frontHit ) {backHit--;}
409 
410  double rFirst = (*frontHit)->globalPosition().mag();
411  double rLast = (*backHit) ->globalPosition().mag();
412 
413  if ( rFirst < rLast ) return inToOut;
414  else if (rFirst > rLast) return outToIn;
415  else {
416  LogError(theCategory) << "Impossible to determine the rechits order" << endl;
417  return undetermined;
418  }
419  }
420  else {
421  LogError(theCategory) << "Impossible to determine the rechits order" << endl;
422  return undetermined;
423  }
424 }
425 
426 
427 //
428 // select trajectories with only a single TEC hit
429 //
432 
433  int nTEC(0);
434 
436  for (ConstRecHitContainer::const_iterator i = all.begin(); i != all.end(); i++) {
437  if ( !(*i)->isValid() ) continue;
438  if ( (*i)->det()->geographicalId().det() == DetId::Tracker &&
439  (*i)->det()->geographicalId().subdetId() == StripSubdetector::TEC) {
440  nTEC++;
441  } else {
442  hits.push_back(*i);
443  }
444  if ( nTEC > 1 ) return all;
445  }
446 
447  return hits;
448 
449 }
450 
451 
452 //
453 // rescale errors of outermost TEC RecHit
454 //
456  double scl_x,
457  double scl_y) const {
458 
459  int nTEC(0);
460  ConstRecHitContainer::iterator lone_tec;
461 
462  for ( ConstRecHitContainer::iterator i = all.begin(); i != all.end(); i++) {
463  if ( !(*i)->isValid() ) continue;
464 
465  if ( (*i)->det()->geographicalId().det() == DetId::Tracker &&
466  (*i)->det()->geographicalId().subdetId() == StripSubdetector::TEC) {
467  lone_tec = i;
468  nTEC++;
469 
470  if ( (i+1) != all.end() && (*(i+1))->isValid() &&
471  (*(i+1))->det()->geographicalId().det() == DetId::Tracker &&
472  (*(i+1))->det()->geographicalId().subdetId() == StripSubdetector::TEC) {
473  nTEC++;
474  break;
475  }
476  }
477 
478  if (nTEC > 1) break;
479  }
480 
481  int hitDet = (*lone_tec)->hit()->geographicalId().det();
482  int hitSubDet = (*lone_tec)->hit()->geographicalId().subdetId();
483  if ( nTEC == 1 && (*lone_tec)->hit()->isValid() &&
484  hitDet == DetId::Tracker && hitSubDet == StripSubdetector::TEC) {
485 
486  // rescale the TEC rechit error matrix in its rotated frame
487  const SiStripRecHit2D* strip = dynamic_cast<const SiStripRecHit2D*>((*lone_tec)->hit());
488  if (strip && strip->det() ) {
489  LocalPoint pos = strip->localPosition();
490  if ((*lone_tec)->detUnit()) {
491  const StripTopology* topology = dynamic_cast<const StripTopology*>(&(*lone_tec)->detUnit()->topology());
492  if (topology) {
493  // rescale the local error along/perp the strip by a factor
494  float angle = topology->stripAngle(topology->strip((*lone_tec)->hit()->localPosition()));
495  LocalError error = strip->localPositionError();
496  LocalError rotError = error.rotate(angle);
497  LocalError scaledError(rotError.xx() * scl_x * scl_x, 0, rotError.yy() * scl_y * scl_y);
498  error = scaledError.rotate(-angle);
503  SiStripRecHit2D* st = new SiStripRecHit2D(pos,error,
504  *strip->det(),
505  strip->cluster());
506  *lone_tec = MuonTransientTrackingRecHit::build((*lone_tec)->det(),st);
507 
508  }
509  }
510  }
511  }
512 
513 }
514 
515 
517 //
518 // get transient RecHits
519 //
522 
524 
525 
526 
527  TrajectoryStateOnSurface currTsos = trajectoryStateTransform::innerStateOnSurface(track, *theService->trackingGeometry(), &*theService->magneticField());
528 
529  auto hitCloner = static_cast<TkTransientTrackingRecHitBuilder const *>(theTrackerRecHitBuilder.product())->cloner();
530  for (trackingRecHit_iterator hit = track.recHitsBegin(); hit != track.recHitsEnd(); ++hit) {
531  if((*hit)->isValid()) {
532  DetId recoid = (*hit)->geographicalId();
533  if ( recoid.det() == DetId::Tracker ) {
534  if (!(*hit)->hasPositionAndError()){
535  TrajectoryStateOnSurface predTsos = theService->propagator(theTrackerPropagatorName)->propagate(currTsos, theService->trackingGeometry()->idToDet(recoid)->surface());
536 
537  if ( !predTsos.isValid() ) {
538  edm::LogError("MissingTransientHit")
539  <<"Could not get a tsos on the hit surface. We will miss a tracking hit.";
540  continue;
541  }
542  currTsos = predTsos;
543  result.emplace_back(hitCloner(**hit,predTsos));
544  }else{
545  result.push_back((*hit)->cloneSH());
546  }
547  } else if ( recoid.det() == DetId::Muon ) {
548  if ( (*hit)->geographicalId().subdetId() == 3 && !theRPCInTheFit) {
549  LogDebug(theCategory) << "RPC Rec Hit discarded";
550  continue;
551  }
552  result.push_back(theMuonRecHitBuilder->build(&**hit));
553  }
554  }
555  }
556 
557  return result;
558 }
#define LogDebug(id)
const reco::TrackRef muonTrack() const
return muon track
Definition: MuonCandidate.h:43
RectangularEtaPhiTrackingRegion * region(const reco::TrackRef &) const
define tracking region
T getParameter(std::string const &) const
MuonCandidate::CandidateContainer CandidateContainer
void printHits(const ConstRecHitContainer &) const
print all RecHits of a trajectory
T getUntrackedParameter(std::string const &, T const &) const
int i
Definition: DBlmapReader.cc:9
float xx() const
Definition: LocalError.h:24
ConstRecHitContainer selectTrackerHits(const ConstRecHitContainer &) const
select tracker hits; exclude some tracker hits in the global trajectory
virtual void setEvent(const edm::Event &)
pass the Event to the algo at each event
std::pair< const Trajectory *, reco::TrackRef > TrackCand
virtual float stripAngle(float strip) const =0
TrajectoryStateOnSurface innerStateOnSurface(const reco::Track &tk, const TrackingGeometry &geom, const MagneticField *field)
CaloTopology const * topology(0)
edm::ESHandle< TransientTrackingRecHitBuilder > theMuonRecHitBuilder
float LnChiSquaredProbability(double chiSquared, double nrDOF)
virtual void setEvent(const edm::Event &)
pass the Event to the algo at each event
T y() const
Definition: PV3DBase.h:63
void setServices(const edm::EventSetup &)
set the services needed by the TrackTransformer
std::vector< TrackCand > chooseRegionalTrackerTracks(const TrackCand &, const std::vector< TrackCand > &)
choose tracker tracks within region of interest
virtual float strip(const LocalPoint &) const =0
const reco::TrackRef trackerTrack() const
return tracker track
Definition: MuonCandidate.h:46
void fixTEC(ConstRecHitContainer &all, double scl_x, double scl_y) const
rescale errors of outermost TEC RecHit
double trackProbability(const Trajectory &) const
calculate chi2 probability (-ln(P))
float yy() const
Definition: LocalError.h:26
virtual ~GlobalTrajectoryBuilderBase()
destructor
T sqrt(T t)
Definition: SSEVec.h:48
Trajectory * trackerTrajectory() const
return tracker trajectory
Definition: MuonCandidate.h:49
T z() const
Definition: PV3DBase.h:64
tuple result
Definition: query.py:137
TransientTrackingRecHit::ConstRecHitContainer getTransientRecHits(const reco::Track &) const
get transient RecHits of a Track
virtual void setServices(const edm::EventSetup &)
set the services needed by the TrackTransformer
trackingRecHit_iterator recHitsBegin() const
Iterator to first hit on the track.
Definition: Track.h:63
std::vector< MuonCandidate * > CandidateContainer
Definition: MuonCandidate.h:20
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger but the state exists so we define the behavior If all triggers are the negative crieriion will lead to accepting the event(this again matches the behavior of"!*"before the partial wildcard feature was incorporated).The per-event"cost"of each negative criterion with multiple relevant triggers is about the same as!*was in the past
#define LogTrace(id)
TransientTrackingRecHit::ConstRecHitContainer ConstRecHitContainer
std::vector< ConstRecHitPointer > ConstRecHitContainer
GlobalTrajectoryBuilderBase(const edm::ParameterSet &, const MuonServiceProxy *, edm::ConsumesCollector &)
constructor with Parameter Set and MuonServiceProxy
double deltaR(double eta1, double eta2, double phi1, double phi2)
Definition: TreeUtility.cc:17
virtual void setEvent(const edm::Event &)
pass the Event to the algo at each event
Definition: DetId.h:18
int ndof(bool bon=true) const
Definition: Trajectory.cc:78
GlobalMuonTrackMatcher * theTrackMatcher
T const * product() const
Definition: ESHandle.h:62
MuonTrajectoryBuilder::CandidateContainer build(const TrackCand &, MuonTrajectoryBuilder::CandidateContainer &) const
build combined trajectory from sta Track and tracker RecHits
void setSeedRef(const edm::RefToBase< TrajectorySeed > &seedRef)
Definition: Trajectory.h:308
float chiSquared() const
Definition: Trajectory.h:252
RectangularEtaPhiTrackingRegion defineRegionOfInterest(const reco::TrackRef &) const
define region of interest with tracker
virtual std::vector< Trajectory > transform(const reco::Track &) const
Convert a reco::Track into Trajectory.
RefitDirection checkRecHitsOrdering(const ConstRecHitContainer &) const
This does nothing now.
MuonTrackingRegionBuilder * theRegionBuilder
Trajectory * trajectory() const
return trajectory
Definition: MuonCandidate.h:40
LocalError rotate(float x, float y) const
Return a new LocalError, rotated by an angle defined by the direction (x,y)
Definition: LocalError.h:39
Detector det() const
get the detector field from this detid
Definition: DetId.h:35
T x() const
Definition: PV3DBase.h:62
static RecHitPointer build(const GeomDet *geom, const TrackingRecHit *rh)
FIXME virtual ConstMuonRecHitContainer specificTransientHits() const;.
std::vector< Trajectory > refit(const reco::Track &globalTrack, const int theMuonHitsOption, const TrackerTopology *tTopo) const
build combined trajectory from sta Track and tracker RecHits
edm::ESHandle< TransientTrackingRecHitBuilder > theTrackerRecHitBuilder
T angle(T x1, T y1, T z1, T x2, T y2, T z2)
Definition: angle.h:11
trackingRecHit_iterator recHitsEnd() const
Iterator to last hit on the track.
Definition: Track.h:65