]> git.uio.no Git - u/mrichter/AliRoot.git/blob - PWGCF/FEMTOSCOPY/AliFemto/AliFmHelix.cxx
Fixes for coverity.
[u/mrichter/AliRoot.git] / PWGCF / FEMTOSCOPY / AliFemto / AliFmHelix.cxx
1 ///////////////////////////////////////////////////////////////////////////
2 //                                                                       //
3 // AliFmHelix: a helper helix class                                      //
4 // Includes all the operations and specifications of the helix. Can be   //
5 // used to determine path lengths, distance of closest approach etc.     //
6 //                                                                       //
7 ///////////////////////////////////////////////////////////////////////////
8 #if !defined(ST_NO_NUMERIC_LIMITS)
9 #    include <limits>
10 #    if !defined(ST_NO_NAMESPACES)
11 using std::numeric_limits;
12 #    endif
13 #endif
14 #define FOR_HELIX
15 #include <float.h>
16 #include <assert.h>
17
18 #include "AliFmHelix.h"
19 #include "PhysicalConstants.h" 
20 #include "SystemOfUnits.h"
21 #ifdef __ROOT__
22 ClassImpT(AliFmHelix,double);
23 #endif
24
25 #ifdef WIN32
26 #include "gcc2vs.h"
27 #endif
28
29 #include <iostream>
30 #include <fstream>
31 using namespace std;
32
33 const double AliFmHelix::fgkNoSolution = 3.e+33;
34
35 AliFmHelix::AliFmHelix() :
36   fSingularity(0),
37   fOrigin(0,0,0),
38   fDipAngle(0),
39   fCurvature(0),
40   fPhase(0),
41   fH(0),
42   fCosDipAngle(0),
43   fSinDipAngle(0),
44   fCosPhase(0),
45   fSinPhase(0)
46 {
47   //Default constructor
48 /*noop*/ 
49 }
50
51 AliFmHelix::AliFmHelix(double c, double d, double phase,
52                        const AliFmThreeVector<double>& o, int h) :
53   fSingularity(0),
54   fOrigin(0,0,0),
55   fDipAngle(0),
56   fCurvature(0),
57   fPhase(0),
58   fH(0),
59   fCosDipAngle(0),
60   fSinDipAngle(0),
61   fCosPhase(0),
62   fSinPhase(0)
63 {
64   // Constructor with helix parameters
65   SetParameters(c, d, phase, o, h);
66 }
67
68 AliFmHelix::~AliFmHelix() { 
69   // Default destructor
70 /* noop */ 
71 }
72
73 void AliFmHelix::SetParameters(double c, double dip, double phase,
74                                const AliFmThreeVector<double>& o, int h)
75 {
76         //
77         //  The order in which the parameters are set is important
78         //  since setCurvature might have to adjust the others.
79         //
80         fH = (h>=0) ? 1 : -1;    // Default is: positive particle
81         //             positive field
82         fOrigin   = o;
83         SetDipAngle(dip);
84         SetPhase(phase);
85
86         //
87         // Check for singularity and correct for negative curvature.           
88         // May change mH and mPhase. Must therefore be set last.
89         //
90         SetCurvature(c);
91
92         //
93         // For the case B=0, h is ill defined. In the following we
94         // always assume h = +1. Since phase = psi - h * pi/2
95         // we have to correct the phase in case h = -1.
96         // This assumes that the user uses the same h for phase
97         // as the one he passed to the constructor.
98         //
99         if (fSingularity && fH == -1) {
100                 fH = +1;
101                 SetPhase(fPhase-M_PI);
102         }
103 }
104
105 void AliFmHelix::SetCurvature(double val)
106 {
107   // Set helix curvature
108         if (val < 0) {
109                 fCurvature = -val;
110                 fH = -fH;
111                 SetPhase(fPhase+M_PI);
112         }
113         else
114                 fCurvature = val;
115
116 #ifndef ST_NO_NUMERIC_LIMITS
117         if (fabs(fCurvature) <= numeric_limits<double>::epsilon())
118 #else
119         if (fabs(fCurvature) <= static_cast<double>(0))
120 #endif    
121                 fSingularity = true;                    // straight line
122         else
123                 fSingularity = false;                   // curved
124 }
125
126 void AliFmHelix::SetPhase(double val)
127 {
128   // Set helix phase
129         fPhase       = val;
130         fCosPhase    = cos(fPhase);
131         fSinPhase    = sin(fPhase);
132         if (fabs(fPhase) > M_PI)
133                 fPhase = atan2(fSinPhase, fCosPhase);  // force range [-pi,pi]
134 }
135
136 void AliFmHelix::SetDipAngle(double val)
137 {
138   // Set helix dip angle
139         fDipAngle    = val;
140         fCosDipAngle = cos(fDipAngle);
141         fSinDipAngle = sin(fDipAngle);
142 }
143
144 double AliFmHelix::XCenter() const
145 {
146   // Set helix center in X
147         if (fSingularity)
148                 return 0;
149         else
150                 return fOrigin.x()-fCosPhase/fCurvature;
151 }
152
153 double AliFmHelix::YCenter() const
154 {
155   // Set helix center in Y
156         if (fSingularity)
157                 return 0;
158         else
159                 return fOrigin.y()-fSinPhase/fCurvature;
160 }
161
162 double AliFmHelix::FudgePathLength(const AliFmThreeVector<double>& p) const
163 {
164   // Path length
165         double s;
166         double dx = p.x()-fOrigin.x();
167         double dy = p.y()-fOrigin.y();
168
169         if (fSingularity) {
170                 s = (dy*fCosPhase - dx*fSinPhase)/fCosDipAngle;
171         }
172         else {
173                 s = atan2(dy*fCosPhase - dx*fSinPhase,
174                         1/fCurvature + dx*fCosPhase+dy*fSinPhase)/
175                         (fH*fCurvature*fCosDipAngle);
176         }
177         return s;
178 }
179
180 double AliFmHelix::Distance(const AliFmThreeVector<double>& p, bool scanPeriods) const
181 {
182   // calculate distance between origin an p along the helix
183         return abs(this->At(PathLength(p,scanPeriods))-p);
184 }
185
186 double AliFmHelix::PathLength(const AliFmThreeVector<double>& p, bool scanPeriods) const 
187 {
188         //
189         //  Returns the path length at the distance of closest 
190         //  approach between the helix and point p. 
191         //  For the case of B=0 (straight line) the path length
192         //  can be calculated analytically. For B>0 there is
193         //  unfortunately no easy solution to the problem.
194         //  Here we use the Newton method to find the root of the
195         //  referring equation. The 'fudgePathLength' serves
196         //  as a starting value.
197         //
198         double s;
199         double dx = p.x()-fOrigin.x();
200         double dy = p.y()-fOrigin.y();
201         double dz = p.z()-fOrigin.z();
202
203         if (fSingularity) {
204                 s = fCosDipAngle*(fCosPhase*dy-fSinPhase*dx) +
205                         fSinDipAngle*dz;
206         }
207         else { //
208 #ifndef ST_NO_NAMESPACES
209                 {
210                         using namespace units;
211 #endif
212                         const double ktMaxPrecisionNeeded = micrometer;
213                         const int    ktMaxIterations      = 100;
214
215                         //
216                         // The math is taken from Maple with C(expr,optimized) and
217                         // some hand-editing. It is not very nice but efficient.
218                         //
219                         double t34 = fCurvature*fCosDipAngle*fCosDipAngle;
220                         double t41 = fSinDipAngle*fSinDipAngle;
221                         double t6, t7, t11, t12, t19;
222
223                         //
224                         // Get a first guess by using the dca in 2D. Since
225                         // in some extreme cases we might be off by n periods
226                         // we add (subtract) periods in case we get any closer.
227                         // 
228                         s = FudgePathLength(p);
229
230                         if (scanPeriods) {
231                                 double ds = Period();
232                                 int    j, jmin = 0;
233                                 double d, dmin = abs(At(s) - p);
234                                 for(j=1; j<ktMaxIterations; j++) {
235                                         if ((d = abs(At(s+j*ds) - p)) < dmin) {
236                                                 dmin = d;
237                                                 jmin = j;
238                                         }
239                                         else
240                                                 break;
241                                 }
242                                 for(j=-1; -j<ktMaxIterations; j--) {
243                                         if ((d = abs(At(s+j*ds) - p)) < dmin) {
244                                                 dmin = d;
245                                                 jmin = j;
246                                         }
247                                         else
248                                                 break;
249                                 }
250                                 if (jmin) s += jmin*ds;
251                         }
252
253                         //
254                         // Newtons method:
255                         // Stops after ktMaxIterations iterations or if the required
256                         // precision is obtained. Whatever comes first.
257                         //
258                         double sOld = s;
259                         for (int i=0; i<ktMaxIterations; i++) {
260                                 t6  = fPhase+s*fH*fCurvature*fCosDipAngle;
261                                 t7  = cos(t6);
262                                 t11 = dx-(1/fCurvature)*(t7-fCosPhase);
263                                 t12 = sin(t6);
264                                 t19 = dy-(1/fCurvature)*(t12-fSinPhase);
265                                 s  -= (t11*t12*fH*fCosDipAngle-t19*t7*fH*fCosDipAngle -
266                                         (dz-s*fSinDipAngle)*fSinDipAngle)/
267                                         (t12*t12*fCosDipAngle*fCosDipAngle+t11*t7*t34 +
268                                         t7*t7*fCosDipAngle*fCosDipAngle +
269                                         t19*t12*t34+t41);
270                                 if (fabs(sOld-s) < ktMaxPrecisionNeeded) break;
271                                 sOld = s;
272                         }
273 #ifndef ST_NO_NAMESPACES
274                 }
275 #endif
276         }
277         return s;
278 }
279
280 double AliFmHelix::Period() const
281 {
282   // period
283         if (fSingularity)
284 #ifndef ST_NO_NUMERIC_LIMITS
285                 return numeric_limits<double>::max();
286 #else
287                 return DBL_MAX;
288 #endif    
289         else    
290                 return fabs(2*M_PI/(fH*fCurvature*fCosDipAngle)); 
291 }
292
293 pair<double, double> AliFmHelix::PathLength(double r) const
294 {
295         //
296         // The math is taken from Maple with C(expr,optimized) and
297         // some hand-editing. It is not very nice but efficient.
298         // 'first' is the smallest of the two solutions (may be negative)
299         // 'second' is the other.
300         //
301         pair<double,double> tvalue;
302         pair<double,double> tVALUE(999999999.,999999999.);
303         if (fSingularity) {
304                 double t1 = fCosDipAngle*(fOrigin.x()*fSinPhase-fOrigin.y()*fCosPhase);
305                 double t12 = fOrigin.y()*fOrigin.y();
306                 double t13 = fCosPhase*fCosPhase;
307                 double t15 = r*r;
308                 double t16 = fOrigin.x()*fOrigin.x();
309                 double t20 = -fCosDipAngle*fCosDipAngle*(2.0*fOrigin.x()*fSinPhase*fOrigin.y()*fCosPhase +
310                         t12-t12*t13-t15+t13*t16);
311                 if (t20<0.) return tVALUE;
312                 t20 = ::sqrt(t20);
313                 tvalue.first  = (t1-t20)/(fCosDipAngle*fCosDipAngle);
314                 tvalue.second = (t1+t20)/(fCosDipAngle*fCosDipAngle);
315         }
316         else {
317                 double t1 = fOrigin.y()*fCurvature;
318                 double t2 = fSinPhase;
319                 double t3 = fCurvature*fCurvature;
320                 double t4 = fOrigin.y()*t2;
321                 double t5 = fCosPhase;
322                 double t6 = fOrigin.x()*t5;
323                 double t8 = fOrigin.x()*fOrigin.x();
324                 double t11 = fOrigin.y()*fOrigin.y();
325                 double t14 = r*r;
326                 double t15 = t14*fCurvature;
327                 double t17 = t8*t8;
328                 double t19 = t11*t11;
329                 double t21 = t11*t3;
330                 double t23 = t5*t5;
331                 double t32 = t14*t14;
332                 double t35 = t14*t3;
333                 double t38 = 8.0*t4*t6 - 4.0*t1*t2*t8 - 4.0*t11*fCurvature*t6 +
334                         4.0*t15*t6 + t17*t3 + t19*t3 + 2.0*t21*t8 + 4.0*t8*t23 -
335                         4.0*t8*fOrigin.x()*fCurvature*t5 - 4.0*t11*t23 -
336                         4.0*t11*fOrigin.y()*fCurvature*t2 + 4.0*t11 - 4.0*t14 +
337                         t32*t3 + 4.0*t15*t4 - 2.0*t35*t11 - 2.0*t35*t8;
338                 double t40 = (-t3*t38);
339                 if (t40<0.) return tVALUE;
340                 t40 = ::sqrt(t40);
341
342                 double t43 = fOrigin.x()*fCurvature;
343                 double t45 = 2.0*t5 - t35 + t21 + 2.0 - 2.0*t1*t2 -2.0*t43 - 2.0*t43*t5 + t8*t3;
344                 double t46 = fH*fCosDipAngle*fCurvature;
345
346                 tvalue.first = (-fPhase + 2.0*atan((-2.0*t1 + 2.0*t2 + t40)/t45))/t46;
347                 tvalue.second = -(fPhase + 2.0*atan((2.0*t1 - 2.0*t2 + t40)/t45))/t46;
348
349                 //
350                 //   Solution can be off by +/- one Period, select smallest
351                 //
352                 double p = Period();
353                 //      malisa deletes "isnan" check 22apr2006  if (!isnan(value.first)) {
354                 if (fabs(tvalue.first-p) < fabs(tvalue.first)) tvalue.first = tvalue.first-p;
355                 else if (fabs(tvalue.first+p) < fabs(tvalue.first)) tvalue.first = tvalue.first+p;
356                 //      malisa  }
357                 //      malisa deletes "isnan" check 22apr2006          if (!isnan(tvalue.second)) {
358                 if (fabs(tvalue.second-p) < fabs(tvalue.second)) tvalue.second = tvalue.second-p;
359                 else if (fabs(tvalue.second+p) < fabs(tvalue.second)) tvalue.second = tvalue.second+p;
360                 //      malisa }
361         }
362         if (tvalue.first > tvalue.second)
363                 swap(tvalue.first,tvalue.second);
364         return(tvalue);
365 }
366
367 pair<double, double> AliFmHelix::PathLength(double r, double x, double y, bool /* scanPeriods */)
368 {
369   // path length
370         double x0 = fOrigin.x();
371         double y0 = fOrigin.y();
372         fOrigin.SetX(x0-x);
373         fOrigin.SetY(y0-y);
374         pair<double, double> result = this->PathLength(r);
375         fOrigin.SetX(x0);
376         fOrigin.SetY(y0);
377         return result;  
378 }
379
380 double AliFmHelix::PathLength(const AliFmThreeVector<double>& r,
381                                                    const AliFmThreeVector<double>& n) const
382 {
383         //
384         // Vector 'r' defines the position of the center and
385         // vector 'n' the normal vector of the plane.
386         // For a straight line there is a simple analytical
387         // solution. For curvatures > 0 the root is determined
388         // by Newton method. In case no valid s can be found
389         // the max. largest value for s is returned.
390         //
391         double s;
392
393         if (fSingularity) {
394                 double t = n.z()*fSinDipAngle +
395                         n.y()*fCosDipAngle*fCosPhase -
396                         n.x()*fCosDipAngle*fSinPhase;
397                 if (t == 0)
398                         s = fgkNoSolution;
399                 else
400                         s = ((r - fOrigin)*n)/t;
401         }
402         else {
403                 const double ktMaxPrecisionNeeded = micrometer;
404                 const int    ktMaxIterations      = 20;
405
406                 double tA = fCurvature*((fOrigin - r)*n) -
407                         n.x()*fCosPhase - 
408                         n.y()*fSinPhase;
409                 double t = fH*fCurvature*fCosDipAngle;
410                 double u = n.z()*fCurvature*fSinDipAngle;
411
412                 double a, f, fp;
413                 double sOld = s = 0;  
414                 double shiftOld = 0;
415                 double shift;
416                 //              (cos(kangMax)-1)/kangMax = 0.1
417                 const double kangMax = 0.21;
418                 double deltas = fabs(kangMax/(fCurvature*fCosDipAngle));
419                 //              dampingFactor = exp(-0.5);
420                 double dampingFactor = 0.60653;
421                 int i;
422
423                 for (i=0; i<ktMaxIterations; i++) {
424                         a  = t*s+fPhase;
425                         double sina = sin(a);
426                         double cosa = cos(a);
427                         f  = tA +
428                                 n.x()*cosa +
429                                 n.y()*sina +
430                                 u*s;
431                         fp = -n.x()*sina*t +
432                                 n.y()*cosa*t +
433                                 u;
434                         if ( fabs(fp)*deltas <= fabs(f) ) { //too big step
435                                 int sgn = 1;
436                                 if (fp<0.) sgn = -sgn;
437                                 if (f <0.) sgn = -sgn;
438                                 shift = sgn*deltas;
439                                 if (shift == -shiftOld) { // don't get stuck shifting +/-deltas
440                                         deltas *= dampingFactor; // dampen magnitude of shift
441                                         shift = sgn*deltas;
442                                         // allow iterations to run out
443                                 } else {
444                                         i--; // don't count against iterations
445                                 }
446                         } else {
447                                 shift = f/fp;
448                         }
449                         s -= shift;
450                         shiftOld = shift;
451                         if (fabs(sOld-s) < ktMaxPrecisionNeeded) break;
452                         sOld = s;
453                 }
454                 if (i == ktMaxIterations) return fgkNoSolution;
455         }
456         return s;
457 }
458
459 pair<double, double>
460 AliFmHelix::PathLengths(const AliFmHelix& h, bool scanPeriods) const
461 {
462         //
463         //      Cannot handle case where one is a helix
464         //  and the other one is a straight line.
465         //
466         if (fSingularity != h.fSingularity) 
467                 return pair<double, double>(fgkNoSolution, fgkNoSolution);
468
469         double s1, s2;
470
471         if (fSingularity) {
472                 //
473                 //  Analytic solution
474                 //
475                 AliFmThreeVector<double> dv = h.fOrigin - fOrigin;
476                 AliFmThreeVector<double> a(-fCosDipAngle*fSinPhase,
477                         fCosDipAngle*fCosPhase,
478                         fSinDipAngle);
479                 AliFmThreeVector<double> b(-h.fCosDipAngle*h.fSinPhase,
480                         h.fCosDipAngle*h.fCosPhase,
481                         h.fSinDipAngle);        
482                 double ab = a*b;
483                 double g  = dv*a;
484                 double k  = dv*b;
485                 s2 = (k-ab*g)/(ab*ab-1.);
486                 s1 = g+s2*ab;
487                 return pair<double, double>(s1, s2);
488         }
489         else {  
490                 //
491                 //  First step: get dca in the xy-plane as start value
492                 //
493                 double dx = h.XCenter() - XCenter();
494                 double dy = h.YCenter() - YCenter();
495                 double dd = ::sqrt(dx*dx + dy*dy);
496                 double r1 = 1/Curvature();
497                 double r2 = 1/h.Curvature();
498
499                 /* malisa 22apr2006 is commenting out the "isnan" check
500                  *              if ( !finite(r1) || isnan(r1) || !finite(r2) || isnan(r2) ) {
501                  *               cerr << __FUNCTION__ << " *** error *** ";
502                  *                      cerr << "  r1=" << r1;
503                  *                      cerr << "  r2=" << r2 << endl;
504                  *                      return pair<double, double>(fgkNoSolution, fgkNoSolution);
505                  *              }
506                  */
507
508                 double cosAlpha = (r1*r1 + dd*dd - r2*r2)/(2*r1*dd);
509
510                 double s;
511                 double x, y;
512                 if (fabs(cosAlpha) < 1) {           // two solutions
513                         double sinAlpha = sin(acos(cosAlpha));
514                         x = XCenter() + r1*(cosAlpha*dx - sinAlpha*dy)/dd;
515                         y = YCenter() + r1*(sinAlpha*dx + cosAlpha*dy)/dd;
516                         s = PathLength(x, y);
517                         x = XCenter() + r1*(cosAlpha*dx + sinAlpha*dy)/dd;
518                         y = YCenter() + r1*(cosAlpha*dy - sinAlpha*dx)/dd;
519                         double a = PathLength(x, y);
520                         if (h.Distance(At(a),scanPeriods) < h.Distance(At(s),scanPeriods)) s = a;
521                 }
522                 else {                              // no intersection (or exactly one)
523                         x = XCenter() + r1*dx/dd;
524                         y = YCenter() + r1*dy/dd;
525                         s = PathLength(x, y);
526                 }
527
528                 //
529                 //   Second step: scan in decreasing intervals around seed 's'
530                 // 
531                 const double ktMinStepSize = 10*micrometer;
532                 const double ktMinRange    = 10*centimeter;    
533                 double dmin              = h.Distance(At(s),scanPeriods);
534                 double range             = max(2*dmin, ktMinRange);
535
536                 /* malisa comments out the "isnan" check 22apr2006
537                  *              if ( !finite(range) || isnan(range)) {
538                  *                      cerr << __FUNCTION__ << " *** error *** ";
539                  *                      cerr << "  range=" << range << endl;
540                  *                      return pair<double, double>(fgkNoSolution, fgkNoSolution);
541                  *              }
542                  */
543
544                 double ds = range/10.;
545                 double slast=-999999, d;
546                 s1 = s - range/2.;
547                 s2 = s + range/2.;
548
549                 double tmp1;
550                 double tmp2;
551
552                 while (ds > ktMinStepSize) {
553                         if ( !(s1<s2) ) {
554                                 cerr << __FUNCTION__ << " *** error *** s1 = ";
555                                 cerr << "    s2 = " << s2;
556                                 cerr << "    range = " << range;
557                                 cerr << "     ds = " << ds << endl;
558                                 return pair<double, double>(fgkNoSolution, fgkNoSolution);
559                         }       
560                         tmp1 = (s2-s1);
561                         tmp2 = tmp1/10.0;
562                         ds = tmp2;
563                         if ( ds<0) {
564                             cerr << __FUNCTION__ << " *** error *** ds = " << ds << endl;
565                             return pair<double, double>(fgkNoSolution, fgkNoSolution);
566                         }
567                         int iterations = 0;
568                         for (double ss=s1; ss<(s2+ds); ss+=ds) {
569                             iterations++;
570                             if ( iterations > 100 ) {
571                                 cerr << __FUNCTION__ << " *** error *** iterations = " << iterations << endl;
572                                 return pair<double, double>(fgkNoSolution, fgkNoSolution);
573                             }
574                             d = h.Distance(At(ss),scanPeriods);
575                             if (d < dmin) {
576                                 dmin = d;
577                                 s = ss;
578                             }
579                             slast = ss;
580                         }
581                         //
582                         //  In the rare cases where the minimum is at the
583                         //  the border of the current range we shift the range
584                         //  and start all over, i.e we do not decrease 'ds'.
585                         //  Else we decrease the search intervall around the
586                         //  current minimum and redo the scan in smaller steps.
587                         //
588                         if (s == s1) {
589                                 d = 0.8*(s2-s1);
590                                 s1 -= d;
591                                 s2 -= d;
592                         }
593                         else if (s == slast) {
594                                 d = 0.8*(s2-s1);
595                                 s1 += d;
596                                 s2 += d;
597                         }
598                         else {           
599                                 s1 = s-ds;
600                                 s2 = s+ds;
601                         //      ds /= 10;
602                         }
603                 }
604                 return pair<double, double>(s, h.PathLength(At(s),scanPeriods));
605         }
606 }
607
608
609 void AliFmHelix::MoveOrigin(double s)
610 {
611   // Move the helix origin
612         if (fSingularity)
613                 fOrigin = At(s);
614         else {
615                 AliFmThreeVector<double> newOrigin = At(s);
616                 double newPhase = atan2(newOrigin.y() - YCenter(),
617                         newOrigin.x() - XCenter());
618                 fOrigin = newOrigin;
619                 SetPhase(newPhase);             
620         }
621 }
622
623 int operator== (const AliFmHelix& a, const AliFmHelix& b)
624 {
625         //
626         // Checks for numerical identity only !
627         //
628         return (a.Origin()    == b.Origin()    &&
629                 a.DipAngle()  == b.DipAngle()  &&
630                 a.Curvature() == b.Curvature() &&
631                 a.Phase()     == b.Phase()     &&
632                 a.H()         == b.H());
633 }
634
635 int operator!= (const AliFmHelix& a, const AliFmHelix& b) {return !(a == b);}
636
637 ostream& operator<<(ostream& os, const AliFmHelix& h)
638 {
639         return os << '('
640                 << "curvature = "  << h.Curvature() << ", " 
641                 << "dip angle = "  << h.DipAngle()  << ", "
642                 << "phase = "      << h.Phase()     << ", "  
643                 << "h = "          << h.H()         << ", "    
644                 << "origin = "     << h.Origin()    << ')';
645 }
646
647
648
649
650