MpdFemtoHelix.cxx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /**
  2. * \class MpdFemtoHelix
  3. * \author Grigory Nigmatkulov, May 07 2018
  4. *
  5. * Parametrization of a helix (modification of StHelix). Can also cope
  6. * with straight tracks, i.e. with zero curvature. This represents only
  7. * the mathematical model of a helix. See the SCL user guide for more.
  8. *
  9. */
  10. // C++ headers
  11. #if !defined(ST_NO_NUMERIC_LIMITS)
  12. #include <limits>
  13. #if !defined(ST_NO_NAMESPACES)
  14. using std::numeric_limits;
  15. #endif
  16. #endif
  17. #define FOR_PICO_HELIX
  18. // C++ headers
  19. #include <float.h>
  20. #include <iostream>
  21. // PicoDst headers
  22. #include "MpdFemtoHelix.h"
  23. //#ifdef _VANILLA_ROOT_
  24. #include "PhysicalConstants.h"
  25. //#else
  26. //#include "StarClassLibrary/PhysicalConstants.h"
  27. //#endif
  28. ClassImp(MpdFemtoHelix)
  29. const double MpdFemtoHelix::NoSolution = 3.e+33;
  30. //_________________
  31. MpdFemtoHelix::MpdFemtoHelix() : mSingularity(false), mOrigin(0, 0, 0),
  32. mDipAngle(0), mCurvature(0), mPhase(0),
  33. mH(0), mCosDipAngle(0), mSinDipAngle(0),
  34. mCosPhase(0), mSinPhase(0) {
  35. /*no-op*/
  36. }
  37. //_________________
  38. MpdFemtoHelix::MpdFemtoHelix(double c, double d, double phase,
  39. const TVector3& o, int h) {
  40. setParameters(c, d, phase, o, h);
  41. }
  42. //_________________
  43. MpdFemtoHelix::MpdFemtoHelix(const MpdFemtoHelix &h) {
  44. mSingularity = h.mSingularity;
  45. mOrigin = h.mOrigin;
  46. mDipAngle = h.mDipAngle;
  47. mCurvature = h.mCurvature;
  48. mPhase = h.mPhase;
  49. mH = h.mH;
  50. mCosDipAngle = h.mCosDipAngle;
  51. mSinDipAngle = h.mSinDipAngle;
  52. mCosPhase = h.mCosPhase;
  53. mSinPhase = h.mSinPhase;
  54. }
  55. //_________________
  56. MpdFemtoHelix::~MpdFemtoHelix() {
  57. /* noop */
  58. };
  59. //_________________
  60. void MpdFemtoHelix::setParameters(double c, double dip, double phase,
  61. const TVector3& o, int h) {
  62. //
  63. // The order in which the parameters are set is important
  64. // since setCurvature might have to adjust the others.
  65. //
  66. mH = (h >= 0) ? 1 : -1; // Default is: positive particle
  67. // positive field
  68. mOrigin = o;
  69. setDipAngle(dip);
  70. setPhase(phase);
  71. //
  72. // Check for singularity and correct for negative curvature.
  73. // May change mH and mPhase. Must therefore be set last.
  74. //
  75. setCurvature(c);
  76. //
  77. // For the case B=0, h is ill defined. In the following we
  78. // always assume h = +1. Since phase = psi - h * pi/2
  79. // we have to correct the phase in case h = -1.
  80. // This assumes that the user uses the same h for phase
  81. // as the one he passed to the constructor.
  82. //
  83. if (mSingularity && mH == -1) {
  84. mH = +1;
  85. setPhase(mPhase - M_PI);
  86. }
  87. }
  88. //_________________
  89. void MpdFemtoHelix::setCurvature(double val) {
  90. if (val < 0) {
  91. mCurvature = -val;
  92. mH = -mH;
  93. setPhase(mPhase + M_PI);
  94. } else {
  95. mCurvature = val;
  96. }
  97. #ifndef ST_NO_NUMERIC_LIMITS
  98. if (::fabs(mCurvature) <= numeric_limits<double>::epsilon()) {
  99. #else
  100. if (::fabs(mCurvature) <= static_cast<double> (0)) {
  101. #endif
  102. mSingularity = true; /// straight line
  103. } else {
  104. mSingularity = false; /// curved
  105. }
  106. }
  107. //_________________
  108. void MpdFemtoHelix::setPhase(double val) {
  109. mPhase = val;
  110. mCosPhase = cos(mPhase);
  111. mSinPhase = sin(mPhase);
  112. if (::fabs(mPhase) > M_PI) {
  113. mPhase = atan2(mSinPhase, mCosPhase); // force range [-pi,pi]
  114. }
  115. }
  116. //_________________
  117. void MpdFemtoHelix::setDipAngle(double val) {
  118. mDipAngle = val;
  119. mCosDipAngle = cos(mDipAngle);
  120. mSinDipAngle = sin(mDipAngle);
  121. }
  122. //_________________
  123. double MpdFemtoHelix::xcenter() const {
  124. if (mSingularity) {
  125. return 0;
  126. } else {
  127. return mOrigin.x() - mCosPhase / mCurvature;
  128. }
  129. }
  130. //_________________
  131. double MpdFemtoHelix::ycenter() const {
  132. if (mSingularity) {
  133. return 0;
  134. } else {
  135. return mOrigin.y() - mSinPhase / mCurvature;
  136. }
  137. }
  138. //_________________
  139. double MpdFemtoHelix::fudgePathLength(const TVector3& p) const {
  140. double s;
  141. double dx = p.x() - mOrigin.x();
  142. double dy = p.y() - mOrigin.y();
  143. if (mSingularity) {
  144. s = (dy * mCosPhase - dx * mSinPhase) / mCosDipAngle;
  145. } else {
  146. s = atan2(dy * mCosPhase - dx*mSinPhase,
  147. 1 / mCurvature + dx * mCosPhase + dy * mSinPhase) /
  148. (mH * mCurvature * mCosDipAngle);
  149. }
  150. return s;
  151. }
  152. //_________________
  153. double MpdFemtoHelix::distance(const TVector3& p, bool scanPeriods) const {
  154. return ( this->at(pathLength(p, scanPeriods)) - p).Mag();
  155. }
  156. //_________________
  157. double MpdFemtoHelix::pathLength(const TVector3& p, bool scanPeriods) const {
  158. //
  159. // Returns the path length at the distance of closest
  160. // approach between the helix and point p.
  161. // For the case of B=0 (straight line) the path length
  162. // can be calculated analytically. For B>0 there is
  163. // unfortunately no easy solution to the problem.
  164. // Here we use the Newton method to find the root of the
  165. // referring equation. The 'fudgePathLength' serves
  166. // as a starting value.
  167. //
  168. double s;
  169. double dx = p.x() - mOrigin.x();
  170. double dy = p.y() - mOrigin.y();
  171. double dz = p.z() - mOrigin.z();
  172. if (mSingularity) {
  173. s = mCosDipAngle * (mCosPhase * dy - mSinPhase * dx) +
  174. mSinDipAngle*dz;
  175. } else {
  176. #ifndef ST_NO_NUMERIC_LIMITS
  177. {
  178. using namespace units;
  179. #endif
  180. const double MaxPrecisionNeeded = micrometer;
  181. const int MaxIterations = 100;
  182. //
  183. // The math is taken from Maple with C(expr,optimized) and
  184. // some hand-editing. It is not very nice but efficient.
  185. //
  186. double t34 = mCurvature * mCosDipAngle*mCosDipAngle;
  187. double t41 = mSinDipAngle*mSinDipAngle;
  188. double t6, t7, t11, t12, t19;
  189. //
  190. // Get a first guess by using the dca in 2D. Since
  191. // in some extreme cases we might be off by n periods
  192. // we add (subtract) periods in case we get any closer.
  193. //
  194. s = fudgePathLength(p);
  195. if (scanPeriods) {
  196. double ds = period();
  197. int j;
  198. int jmin = 0;
  199. double d;
  200. double dmin = (at(s) - p).Mag();
  201. for (j = 1; j < MaxIterations; j++) {
  202. d = (at(s + j * ds) - p).Mag();
  203. if (d < dmin) {
  204. dmin = d;
  205. jmin = j;
  206. } else {
  207. break;
  208. }
  209. } //for(j=1; j<MaxIterations; j++)
  210. for (j = -1; -j < MaxIterations; j--) {
  211. d = (at(s + j * ds) - p).Mag();
  212. if (d < dmin) {
  213. dmin = d;
  214. jmin = j;
  215. } else {
  216. break;
  217. }
  218. } //for(j=-1; -j<MaxIterations; j--)
  219. if (jmin) {
  220. s += jmin*ds;
  221. }
  222. } //if (scanPeriods)
  223. //
  224. // Newtons method:
  225. // Stops after MaxIterations iterations or if the required
  226. // precision is obtained. Whatever comes first.
  227. //
  228. double sOld = s;
  229. for (int i = 0; i < MaxIterations; i++) {
  230. t6 = mPhase + s * mH * mCurvature*mCosDipAngle;
  231. t7 = cos(t6);
  232. t11 = dx - (1 / mCurvature)*(t7 - mCosPhase);
  233. t12 = sin(t6);
  234. t19 = dy - (1 / mCurvature)*(t12 - mSinPhase);
  235. s -= (t11 * t12 * mH * mCosDipAngle - t19 * t7 * mH * mCosDipAngle -
  236. (dz - s * mSinDipAngle) * mSinDipAngle) /
  237. (t12 * t12 * mCosDipAngle * mCosDipAngle + t11 * t7 * t34 +
  238. t7 * t7 * mCosDipAngle * mCosDipAngle +
  239. t19 * t12 * t34 + t41);
  240. if (fabs(sOld - s) < MaxPrecisionNeeded) break;
  241. sOld = s;
  242. } //for (int i=0; i<MaxIterations; i++)
  243. #ifndef ST_NO_NUMERIC_LIMITS
  244. }
  245. #endif
  246. } //else
  247. return s;
  248. }
  249. //_________________
  250. double MpdFemtoHelix::period() const {
  251. if (mSingularity) {
  252. #ifndef ST_NO_NUMERIC_LIMITS
  253. return numeric_limits<double>::max();
  254. #else
  255. return DBL_MAX;
  256. #endif
  257. } else {
  258. return fabs(2 * M_PI / (mH * mCurvature * mCosDipAngle));
  259. }
  260. }
  261. //_________________
  262. pair<double, double> MpdFemtoHelix::pathLength(double r) const {
  263. pair<double, double> value;
  264. pair<double, double> VALUE(999999999., 999999999.);
  265. //
  266. // The math is taken from Maple with C(expr,optimized) and
  267. // some hand-editing. It is not very nice but efficient.
  268. // 'first' is the smallest of the two solutions (may be negative)
  269. // 'second' is the other.
  270. //
  271. if (mSingularity) {
  272. double t1 = mCosDipAngle * (mOrigin.x() * mSinPhase - mOrigin.y() * mCosPhase);
  273. double t12 = mOrigin.y() * mOrigin.y();
  274. double t13 = mCosPhase*mCosPhase;
  275. double t15 = r*r;
  276. double t16 = mOrigin.x() * mOrigin.x();
  277. double t20 = -mCosDipAngle * mCosDipAngle * (2.0 * mOrigin.x() * mSinPhase * mOrigin.y() * mCosPhase +
  278. t12 - t12 * t13 - t15 + t13 * t16);
  279. if (t20 < 0.) {
  280. return VALUE;
  281. }
  282. t20 = ::sqrt(t20);
  283. value.first = (t1 - t20) / (mCosDipAngle * mCosDipAngle);
  284. value.second = (t1 + t20) / (mCosDipAngle * mCosDipAngle);
  285. } else {
  286. double t1 = mOrigin.y() * mCurvature;
  287. double t2 = mSinPhase;
  288. double t3 = mCurvature*mCurvature;
  289. double t4 = mOrigin.y() * t2;
  290. double t5 = mCosPhase;
  291. double t6 = mOrigin.x() * t5;
  292. double t8 = mOrigin.x() * mOrigin.x();
  293. double t11 = mOrigin.y() * mOrigin.y();
  294. double t14 = r*r;
  295. double t15 = t14*mCurvature;
  296. double t17 = t8*t8;
  297. double t19 = t11*t11;
  298. double t21 = t11*t3;
  299. double t23 = t5*t5;
  300. double t32 = t14*t14;
  301. double t35 = t14*t3;
  302. double t38 = 8.0 * t4 * t6 - 4.0 * t1 * t2 * t8 - 4.0 * t11 * mCurvature * t6 +
  303. 4.0 * t15 * t6 + t17 * t3 + t19 * t3 + 2.0 * t21 * t8 + 4.0 * t8 * t23 -
  304. 4.0 * t8 * mOrigin.x() * mCurvature * t5 - 4.0 * t11 * t23 -
  305. 4.0 * t11 * mOrigin.y() * mCurvature * t2 + 4.0 * t11 - 4.0 * t14 +
  306. t32 * t3 + 4.0 * t15 * t4 - 2.0 * t35 * t11 - 2.0 * t35*t8;
  307. double t40 = (-t3 * t38);
  308. if (t40 < 0.) {
  309. return VALUE;
  310. }
  311. t40 = ::sqrt(t40);
  312. double t43 = mOrigin.x() * mCurvature;
  313. double t45 = 2.0 * t5 - t35 + t21 + 2.0 - 2.0 * t1 * t2 - 2.0 * t43 - 2.0 * t43 * t5 + t8*t3;
  314. double t46 = mH * mCosDipAngle*mCurvature;
  315. value.first = (-mPhase + 2.0 * atan((-2.0 * t1 + 2.0 * t2 + t40) / t45)) / t46;
  316. value.second = -(mPhase + 2.0 * atan((2.0 * t1 - 2.0 * t2 + t40) / t45)) / t46;
  317. //
  318. // Solution can be off by +/- one period, select smallest
  319. //
  320. double p = period();
  321. if (!std::isnan(value.first)) {
  322. if (::fabs(value.first - p) < ::fabs(value.first)) {
  323. value.first = value.first - p;
  324. } else if (::fabs(value.first + p) < ::fabs(value.first)) {
  325. value.first = value.first + p;
  326. }
  327. } //if ( ! std::isnan(value.first) )
  328. if (!std::isnan(value.second)) {
  329. if (::fabs(value.second - p) < ::fabs(value.second)) {
  330. value.second = value.second - p;
  331. } else if (::fabs(value.second + p) < ::fabs(value.second)) {
  332. value.second = value.second + p;
  333. }
  334. } //if (! std::isnan(value.second))
  335. } //else
  336. if (value.first > value.second) {
  337. swap(value.first, value.second);
  338. }
  339. return (value);
  340. }
  341. //_________________
  342. pair<double, double> MpdFemtoHelix::pathLength(double r, double x, double y) {
  343. double x0 = mOrigin.x();
  344. double y0 = mOrigin.y();
  345. mOrigin.SetX(x0 - x);
  346. mOrigin.SetY(y0 - y);
  347. pair<double, double> result = this->pathLength(r);
  348. mOrigin.SetX(x0);
  349. mOrigin.SetY(y0);
  350. return result;
  351. }
  352. //________________
  353. double MpdFemtoHelix::pathLength(const TVector3& r,
  354. const TVector3& n) const {
  355. //
  356. // Vector 'r' defines the position of the center and
  357. // vector 'n' the normal vector of the plane.
  358. // For a straight line there is a simple analytical
  359. // solution. For curvatures > 0 the root is determined
  360. // by Newton method. In case no valid s can be found
  361. // the max. largest value for s is returned.
  362. //
  363. double s;
  364. if (mSingularity) {
  365. double t = n.z() * mSinDipAngle +
  366. n.y() * mCosDipAngle * mCosPhase -
  367. n.x() * mCosDipAngle*mSinPhase;
  368. if (t == 0) {
  369. s = NoSolution;
  370. } else {
  371. s = ((r - mOrigin) * n) / t;
  372. }
  373. } else {
  374. const double MaxPrecisionNeeded = micrometer;
  375. const int MaxIterations = 20;
  376. double A = mCurvature * ((mOrigin - r) * n) -
  377. n.x() * mCosPhase -
  378. n.y() * mSinPhase;
  379. double t = mH * mCurvature*mCosDipAngle;
  380. double u = n.z() * mCurvature*mSinDipAngle;
  381. double a, f, fp;
  382. double sOld = s = 0;
  383. // Next line is commented to prevent compiler warnings
  384. // double shiftOld = 0;
  385. double shift;
  386. // (cos(angMax)-1)/angMax = 0.1
  387. const double angMax = 0.21;
  388. double deltas = fabs(angMax / (mCurvature * mCosDipAngle));
  389. // dampingFactor = exp(-0.5);
  390. // double dampingFactor = 0.60653;
  391. int i;
  392. for (i = 0; i < MaxIterations; i++) {
  393. a = t * s + mPhase;
  394. double sina = sin(a);
  395. double cosa = cos(a);
  396. f = A + n.x() * cosa + n.y() * sina + u*s;
  397. fp = -n.x() * sina * t + n.y() * cosa * t + u;
  398. if (fabs(fp) * deltas <= fabs(f)) { //too big step
  399. int sgn = 1;
  400. if (fp < 0.) sgn = -sgn;
  401. if (f < 0.) sgn = -sgn;
  402. shift = sgn*deltas;
  403. if (shift < 0) shift *= 0.9; // don't get stuck shifting +/-deltas
  404. } else {
  405. shift = f / fp;
  406. }
  407. s -= shift;
  408. // Next line is commented to prevent compiler warnings
  409. //shiftOld = shift;
  410. if (::fabs(sOld - s) < MaxPrecisionNeeded) {
  411. break;
  412. }
  413. sOld = s;
  414. } //for (i=0; i<MaxIterations; i++)
  415. if (i == MaxIterations) {
  416. return NoSolution;
  417. }
  418. } //else
  419. return s;
  420. }
  421. //_________________
  422. pair<double, double> MpdFemtoHelix::pathLength(const MpdFemtoHelix& h, double minStepSize,
  423. double minRange) const {
  424. //
  425. // Cannot handle case where one is a helix
  426. // and the other one is a straight line.
  427. //
  428. if (mSingularity != h.mSingularity) {
  429. return pair<double, double>(NoSolution, NoSolution);
  430. }
  431. double s1, s2;
  432. if (mSingularity) {
  433. //
  434. // Analytic solution
  435. //
  436. TVector3 dv = h.mOrigin - mOrigin;
  437. TVector3 a(-mCosDipAngle*mSinPhase,
  438. mCosDipAngle*mCosPhase,
  439. mSinDipAngle);
  440. TVector3 b(-h.mCosDipAngle * h.mSinPhase,
  441. h.mCosDipAngle * h.mCosPhase,
  442. h.mSinDipAngle);
  443. double ab = a*b;
  444. double g = dv*a;
  445. double k = dv*b;
  446. s2 = (k - ab * g) / (ab * ab - 1.);
  447. s1 = g + s2*ab;
  448. return pair<double, double>(s1, s2);
  449. }//if (mSingularity)
  450. else {
  451. //
  452. // First step: get dca in the xy-plane as start value
  453. //
  454. double dx = h.xcenter() - xcenter();
  455. double dy = h.ycenter() - ycenter();
  456. double dd = ::sqrt(dx * dx + dy * dy);
  457. double r1 = 1 / curvature();
  458. double r2 = 1 / h.curvature();
  459. double cosAlpha = (r1 * r1 + dd * dd - r2 * r2) / (2 * r1 * dd);
  460. double s;
  461. double x, y;
  462. if (::fabs(cosAlpha) < 1) { // two solutions
  463. double sinAlpha = sin(acos(cosAlpha));
  464. x = xcenter() + r1 * (cosAlpha * dx - sinAlpha * dy) / dd;
  465. y = ycenter() + r1 * (sinAlpha * dx + cosAlpha * dy) / dd;
  466. s = pathLength(x, y);
  467. x = xcenter() + r1 * (cosAlpha * dx + sinAlpha * dy) / dd;
  468. y = ycenter() + r1 * (cosAlpha * dy - sinAlpha * dx) / dd;
  469. double a = pathLength(x, y);
  470. if (h.distance(at(a)) < h.distance(at(s))) {
  471. s = a;
  472. }
  473. }//if ( ::fabs(cosAlpha) < 1)
  474. else { // no intersection (or exactly one)
  475. int rsign = ((r2 - r1) > dd ? -1 : 1); // set -1 when *this* helix is
  476. // completely contained in the other
  477. x = xcenter() + rsign * r1 * dx / dd;
  478. y = ycenter() + rsign * r1 * dy / dd;
  479. s = pathLength(x, y);
  480. } //else
  481. //
  482. // Second step: scan in decreasing intervals around seed 's'
  483. // minRange and minStepSize are passed as arguments to the method.
  484. // They have default values defined in the header file.
  485. //
  486. double dmin = h.distance(at(s));
  487. double range = max(2 * dmin, minRange);
  488. double ds = range / 10;
  489. double slast = -999999, ss, d;
  490. s1 = s - range / 2.;
  491. s2 = s + range / 2.;
  492. while (ds > minStepSize) {
  493. for (ss = s1; ss < s2 + ds; ss += ds) {
  494. d = h.distance(at(ss));
  495. if (d < dmin) {
  496. dmin = d;
  497. s = ss;
  498. }
  499. slast = ss;
  500. } //for ( ss=s1; ss<s2+ds; ss+=ds )
  501. //
  502. // In the rare cases where the minimum is at the
  503. // the border of the current range we shift the range
  504. // and start all over, i.e we do not decrease 'ds'.
  505. // Else we decrease the search intervall around the
  506. // current minimum and redo the scan in smaller steps.
  507. //
  508. if (s == s1) {
  509. d = 0.8 * (s2 - s1);
  510. s1 -= d;
  511. s2 -= d;
  512. } else if (s == slast) {
  513. d = 0.8 * (s2 - s1);
  514. s1 += d;
  515. s2 += d;
  516. } else {
  517. s1 = s - ds;
  518. s2 = s + ds;
  519. ds /= 10;
  520. }
  521. } //while (ds > minStepSize)
  522. return pair<double, double>(s, h.pathLength(at(s)));
  523. } //else
  524. }
  525. //_________________
  526. void MpdFemtoHelix::moveOrigin(double s) {
  527. if (mSingularity) {
  528. mOrigin = at(s);
  529. } else {
  530. TVector3 newOrigin = at(s);
  531. double newPhase = atan2(newOrigin.y() - ycenter(),
  532. newOrigin.x() - xcenter());
  533. mOrigin = newOrigin;
  534. setPhase(newPhase);
  535. }
  536. }
  537. //_________________
  538. int operator==(const MpdFemtoHelix& a, const MpdFemtoHelix& b) {
  539. //
  540. // Checks for numerical identity only !
  541. //
  542. return ( a.origin() == b.origin() &&
  543. a.dipAngle() == b.dipAngle() &&
  544. a.curvature() == b.curvature() &&
  545. a.phase() == b.phase() &&
  546. a.h() == b.h());
  547. }
  548. //_________________
  549. int operator!=(const MpdFemtoHelix& a, const MpdFemtoHelix& b) {
  550. return !(a == b);
  551. }
  552. //_________________
  553. std::ostream& operator<<(std::ostream& os, const MpdFemtoHelix& h) {
  554. return os << '('
  555. << "curvature = " << h.curvature() << ", "
  556. << "dip angle = " << h.dipAngle() << ", "
  557. << "phase = " << h.phase() << ", "
  558. << "h = " << h.h() << ", "
  559. << "origin = " << h.origin().X()
  560. << " , " << h.origin().Y() << " , " << h.origin().Z() << " "
  561. << ')';
  562. }