1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright held by original author
7 -------------------------------------------------------------------------------
9 This file is part of OpenFOAM.
11 OpenFOAM is free software; you can redistribute it and/or modify it
12 under the terms of the GNU General Public License as published by the
13 Free Software Foundation; either version 2 of the License, or (at your
14 option) any later version.
16 OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 You should have received a copy of the GNU General Public License
22 along with OpenFOAM; if not, write to the Free Software Foundation,
23 Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 \*---------------------------------------------------------------------------*/
27 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
32 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 template <class Cmpt, int length>
35 const char* const TensorN<Cmpt, length>::typeName =
36 ("tensor" + name(length)).c_str();
38 template <class Cmpt, int length>
39 const TensorN<Cmpt, length> TensorN<Cmpt, length>::zero(0);
41 template <class Cmpt, int length>
42 const TensorN<Cmpt, length> TensorN<Cmpt, length>::one(1);
45 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
48 template <class Cmpt, int length>
49 inline TensorN<Cmpt, length>::TensorN()
53 //- Construct given VectorSpace
54 template <class Cmpt, int length>
55 inline TensorN<Cmpt, length>::TensorN
57 const VectorSpace<TensorN<Cmpt, length>, Cmpt, length*length>& vs
60 VectorSpace<TensorN<Cmpt, length>, Cmpt, length*length>(vs)
64 //- Construct from component
65 template <class Cmpt, int length>
66 inline TensorN<Cmpt, length>::TensorN(const Cmpt& tx)
68 VectorSpaceOps<TensorN<Cmpt, length>::nComponents,0>::eqOpS
77 //- Construct from Istream
78 template <class Cmpt, int length>
79 inline TensorN<Cmpt, length>::TensorN(Istream& is)
81 VectorSpace<TensorN<Cmpt, length>, Cmpt, length*length>(is)
85 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
87 //- Return tensor transpose
88 template <class Cmpt, int length>
89 inline TensorN<Cmpt, length> TensorN<Cmpt, length>::T() const
91 TensorN<Cmpt, length> transpose;
94 for (int row = 0; row < TensorN<Cmpt, length>::rowLength; row++)
97 for (int col = 0; col < TensorN<Cmpt, length>::rowLength; col++)
99 transpose.v_[i] = this->v_[j];
101 j += TensorN<Cmpt, length>::rowLength;
108 //- Return tensor diagonal
109 template <class Cmpt, int length>
110 inline DiagTensorN<Cmpt, length> TensorN<Cmpt, length>::diag() const
112 DiagTensorN<Cmpt, length> dt;
115 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
117 dt[i] = this->v_[diagI];
118 diagI += TensorN<Cmpt, length>::rowLength + 1;
124 //- Negative sum the vertical off-diagonal components
125 template <class Cmpt, int length>
126 inline TensorN<Cmpt, length> TensorN<Cmpt, length>::negSumDiag() const
128 TensorN<Cmpt, length> negsumdiag;
130 // Zero main diagonal
132 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
134 negsumdiag.v_[diagI] = 0.0;
135 diagI += TensorN<Cmpt, length>::rowLength + 1;
139 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
142 for (int j = 0; j < TensorN<Cmpt, length>::rowLength; j++)
146 negsumdiag.v_[k] = this->v_[k];
147 negsumdiag.v_[diagI] -= this->v_[k];
150 diagI += TensorN<Cmpt, length>::rowLength + 1;
157 //- Assign to a SphericalTensorN
158 template <class Cmpt, int length>
160 TensorN<Cmpt, length>::operator=(const SphericalTensorN<Cmpt, length>& st)
163 for (int i = 0; i < TensorN<Cmpt, length>::nComponents; i++)
168 diag += TensorN<Cmpt, length>::rowLength + 1;
172 this->v_[i] = pTraits<Cmpt>::zero;
178 //- Assign to a DiagTensorN
179 template <class Cmpt, int length>
181 TensorN<Cmpt, length>::operator=(const DiagTensorN<Cmpt, length>& dt)
185 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
187 for (int j = 0; j < TensorN<Cmpt, length>::rowLength; j++)
195 this->v_[k] = pTraits<Cmpt>::zero;
204 //- Transform the tensor
205 //- The components are assumed to be individual scalars
206 //- i.e. transform has no effect
207 template<class Cmpt, int length>
208 inline TensorN<Cmpt, length> transform
211 const TensorN<Cmpt, length>& v
218 // * * * * * * * * * * * * * * * Member Operators * * * * * * * * * * * * * //
220 template <class Cmpt, int length>
221 inline const Cmpt& TensorN<Cmpt, length>::operator()
227 return this->operator[](i*TensorN<Cmpt, length>::rowLength + j);
231 template <class Cmpt, int length>
232 inline Cmpt& TensorN<Cmpt, length>::operator()
238 return this->operator[](i*TensorN<Cmpt, length>::rowLength + j);
242 // * * * * * * * * * * * * * * * Global Operators * * * * * * * * * * * * * //
244 //- Inner-product between two tensors
245 template <class Cmpt, int length>
247 innerProduct<TensorN<Cmpt, length>, TensorN<Cmpt, length> >::type
248 operator&(const TensorN<Cmpt, length>& t1, const TensorN<Cmpt, length>& t2)
250 TensorN<Cmpt, length> result(TensorN<Cmpt, length>::zero);
254 for (int row = 0; row < TensorN<Cmpt, length>::rowLength; row++)
256 for (int col = 0; col < TensorN<Cmpt, length>::rowLength; col++)
258 Cmpt& r = result.v_[i];
262 for (int row2=0; row2 < TensorN<Cmpt, length>::rowLength; row2++)
264 r += t1.v_[m]*t2.v_[n];
266 n += TensorN<Cmpt, length>::rowLength;
270 j += TensorN<Cmpt, length>::rowLength;
276 //- Inner-product between diagonal tensor and tensor
277 template <class Cmpt, int length>
279 innerProduct<DiagTensorN<Cmpt, length>, TensorN<Cmpt, length> >::type
282 const DiagTensorN<Cmpt, length>& dt1,
283 const TensorN<Cmpt, length>& t2
286 TensorN<Cmpt, length> result;
289 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
291 const Cmpt& xx = dt1.v_[i];
293 for (int j = 0; j < TensorN<Cmpt, length>::rowLength; j++)
295 result.v_[k] = xx*t2.v_[k];
303 //- Inner-product between tensor and diagonal tensor
304 template <class Cmpt, int length>
306 innerProduct<TensorN<Cmpt, length>, DiagTensorN<Cmpt, length> >::type
309 const TensorN<Cmpt, length>& t1,
310 const DiagTensorN<Cmpt, length>& dt2
313 TensorN<Cmpt, length> result;
316 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
318 for (int j = 0; j < TensorN<Cmpt, length>::rowLength; j++)
320 result.v_[k] = t1.v_[k]*dt2.v_[j];
329 //- Inner-product between spherical tensor and tensor
330 template <class Cmpt, int length>
332 innerProduct<SphericalTensorN<Cmpt, length>, TensorN<Cmpt, length> >::type
335 const SphericalTensorN<Cmpt, length>& st1,
336 const TensorN<Cmpt, length>& t2
339 const Cmpt& s = st1.v_[0];
340 TensorN<Cmpt, length> res;
341 VectorSpaceOps<TensorN<Cmpt, length>::nComponents,0>::opSV
353 //- Inner-product between tensor and spherical tensor
354 template <class Cmpt, int length>
356 innerProduct<TensorN<Cmpt, length>, SphericalTensorN<Cmpt, length> >::type
359 const TensorN<Cmpt, length>& t1,
360 const SphericalTensorN<Cmpt, length>& st2
363 const Cmpt& s = st2.v_[0];
364 TensorN<Cmpt, length> res;
365 VectorSpaceOps<TensorN<Cmpt, length>::nComponents,0>::opVS
377 //- Inner-product between a tensor and a vector
378 template <class Cmpt, int length>
380 innerProduct<TensorN<Cmpt, length>, VectorN<Cmpt, length> >::type
381 operator&(const TensorN<Cmpt, length>& t, const VectorN<Cmpt, length>& v)
383 VectorN<Cmpt, length> result(VectorN<Cmpt, length>::zero);
386 for (int row = 0; row < TensorN<Cmpt, length>::rowLength; row++)
388 Cmpt& r = result.v_[row];
390 for (int col = 0; col < TensorN<Cmpt, length>::rowLength; col++)
392 r += t.v_[i]*v.v_[col];
401 //- Inner-product between a vector and a tensor
402 template <class Cmpt, int length>
404 innerProduct<VectorN<Cmpt, length>, TensorN<Cmpt, length> >::type
405 operator&(const VectorN<Cmpt, length>& v, const TensorN<Cmpt, length>& t)
407 VectorN<Cmpt, length> result(VectorN<Cmpt, length>::zero);
409 for (int col = 0; col < TensorN<Cmpt, length>::rowLength; col++)
412 Cmpt& r = result.v_[col];
414 for (int row = 0; row < TensorN<Cmpt, length>::rowLength; row++)
416 r += v.v_[row]*t.v_[j];
417 j += TensorN<Cmpt, length>::rowLength;
425 //- Outer-product between two vectors
426 template <class Cmpt, int length>
428 outerProduct<VectorN<Cmpt, length>, VectorN<Cmpt, length> >::type
429 operator*(const VectorN<Cmpt, length>& v1, const VectorN<Cmpt, length>& v2)
431 TensorN<Cmpt, length> result(TensorN<Cmpt, length>::zero);
434 for (int row = 0; row < TensorN<Cmpt, length>::rowLength; row++)
436 for (int col = 0; col < TensorN<Cmpt, length>::rowLength; col++)
438 result.v_[i] = v1.v_[row]*v2.v_[col];
447 //- Addition of TensorN and TensorN
448 template <class Cmpt, int length>
449 inline TensorN<Cmpt,length>
450 operator+(const TensorN<Cmpt,length>& t1, const TensorN<Cmpt,length>& t2)
452 TensorN<Cmpt,length> res;
453 VectorSpaceOps<TensorN<Cmpt,length>::nComponents,0>::op
465 //- Addition of TensorN and DiagTensorN
466 template <class Cmpt, int length>
467 inline TensorN<Cmpt,length>
468 operator+(const TensorN<Cmpt,length>& t1, const DiagTensorN<Cmpt,length>& dt2)
470 TensorN<Cmpt, length> result(t1);
473 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
475 result.v_[diag] += dt2.v_[i];
476 diag += TensorN<Cmpt, length>::rowLength + 1;
483 //- Addition of DiagTensorN and TensorN
484 template <class Cmpt, int length>
485 inline TensorN<Cmpt,length>
486 operator+(const DiagTensorN<Cmpt,length>& dt1, const TensorN<Cmpt,length>& t2)
488 TensorN<Cmpt, length> result(t2);
491 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
493 result.v_[diag] += dt1.v_[i];
494 diag += TensorN<Cmpt, length>::rowLength + 1;
501 //- Addition of TensorN and SphericalTensorN
502 template <class Cmpt, int length>
503 inline TensorN<Cmpt,length>
506 const TensorN<Cmpt,length>& t1,
507 const SphericalTensorN<Cmpt,length>& st2
510 TensorN<Cmpt, length> result(t1);
512 const Cmpt& s = st2.v_[0];
514 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
516 result.v_[diag] += s;
517 diag += TensorN<Cmpt, length>::rowLength + 1;
524 //- Addition of SphericalTensorN and TensorN
525 template <class Cmpt, int length>
526 inline TensorN<Cmpt,length>
529 const SphericalTensorN<Cmpt,length>& st1,
530 const TensorN<Cmpt,length>& t2
533 TensorN<Cmpt, length> result(t2);
535 const Cmpt& s = st1.v_[0];
537 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
539 result.v_[diag] += s;
540 diag += TensorN<Cmpt, length>::rowLength + 1;
547 //- Subtraction of TensorN and TensorN
548 template <class Cmpt, int length>
549 inline TensorN<Cmpt,length>
550 operator-(const TensorN<Cmpt,length>& t1, const TensorN<Cmpt,length>& t2)
552 TensorN<Cmpt,length> res;
553 VectorSpaceOps<TensorN<Cmpt,length>::nComponents,0>::op
565 //- Subtraction of TensorN and DiagTensorN
566 template <class Cmpt, int length>
567 inline TensorN<Cmpt,length>
568 operator-(const TensorN<Cmpt,length>& t1, const DiagTensorN<Cmpt,length>& dt2)
570 TensorN<Cmpt, length> result(t1);
573 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
575 result.v_[diag] -= dt2.v_[i];
576 diag += TensorN<Cmpt, length>::rowLength + 1;
583 //- Subtraction of DiagTensorN and TensorN
584 template <class Cmpt, int length>
585 inline TensorN<Cmpt,length>
586 operator-(const DiagTensorN<Cmpt,length>& dt1, const TensorN<Cmpt,length>& t2)
588 TensorN<Cmpt, length> result(-t2);
591 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
593 result.v_[diag] += dt1.v_[i];
594 diag += TensorN<Cmpt, length>::rowLength + 1;
601 //- Subtraction of TensorN and SphericalTensorN
602 template <class Cmpt, int length>
603 inline TensorN<Cmpt,length>
606 const TensorN<Cmpt,length>& t1,
607 const SphericalTensorN<Cmpt,length>& st2
610 TensorN<Cmpt, length> result(t1);
612 const Cmpt& s = st2.v_[0];
614 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
616 result.v_[diag] -= s;
617 diag += TensorN<Cmpt, length>::rowLength + 1;
624 //- Subtraction of SphericalTensorN and TensorN
625 template <class Cmpt, int length>
626 inline TensorN<Cmpt,length>
629 const SphericalTensorN<Cmpt,length>& st1,
630 const TensorN<Cmpt,length>& t2
633 TensorN<Cmpt, length> result(-t2);
635 const Cmpt& s = st1.v_[0];
637 for (int i = 0; i < TensorN<Cmpt, length>::rowLength; i++)
639 result.v_[diag] += s;
640 diag += TensorN<Cmpt, length>::rowLength + 1;
647 //- Division of a scalar by a TensorN
648 template <class Cmpt, int length>
649 inline TensorN<Cmpt,length>
650 operator/(const scalar s, const TensorN<Cmpt,length>& t)
655 //- Inner Product of a VectorN by an inverse TensorN
656 template <class Cmpt, int length>
657 inline VectorN<Cmpt,length>
658 operator/(const VectorN<Cmpt,length>& v, const TensorN<Cmpt,length>& t)
663 //- Inner Product of a TensorN by an inverse TensorN
664 template <class Cmpt, int length>
665 inline TensorN<Cmpt,length>
666 operator/(const TensorN<Cmpt,length>& t1, const TensorN<Cmpt,length>& t2)
672 //- Inner Product of a DiagTensorN and an inverse TensorN
673 template <class Cmpt, int length>
674 inline TensorN<Cmpt,length>
675 operator/(const DiagTensorN<Cmpt,length>& dt1, const TensorN<Cmpt,length>& t2)
677 return dt1 & inv(t2);
681 //- Inner Product of a TensorN and an inverse DiagTensorN
682 template <class Cmpt, int length>
683 inline TensorN<Cmpt,length>
684 operator/(const TensorN<Cmpt,length>& t1, const DiagTensorN<Cmpt,length>& dt2)
686 return t1 & inv(dt2);
690 //- Inner Product of a SphericalTensorN and an inverse TensorN
691 template <class Cmpt, int length>
692 inline TensorN<Cmpt,length>
695 const SphericalTensorN<Cmpt,length>& st1,
696 const TensorN<Cmpt,length>& t2
699 return st1.v_[0] * inv(t2);
703 //- Inner Product of a TensorN and an inverse SphericalTensorN
704 template <class Cmpt, int length>
705 inline TensorN<Cmpt,length>
708 const TensorN<Cmpt,length>& t1,
709 const SphericalTensorN<Cmpt,length>& st2
712 TensorN<Cmpt, length> result;
714 const Cmpt& s = st2[0];
715 for (int i = 0; i < TensorN<Cmpt, length>::nComponents; i++)
717 result.v_[i] = t1.v_[i]/s;
724 // UNOPTIMIZED VERSION
726 //- Return the inverse of a tensor give the determinant
727 // Uses Gauss-Jordan Elimination with full pivoting
728 template <class Cmpt, int length>
729 inline TensorN<Cmpt, length> inv(const TensorN<Cmpt, length>& t)
731 TensorN<Cmpt, length> result(t);
733 label i, j, k, iRow=0, iCol=0;
734 Cmpt bigValue, temp, pivotInv;
736 // Lists used for bookkeeping on the pivoting
737 List<label> indexCol(length), indexRow(length), iPivot(length);
741 // Main loop over columns to be reduced
742 for (i=0; i<length; i++)
744 bigValue = pTraits<Cmpt>::zero;
746 //Search for pivot element
747 for (j=0; j<length; j++)
751 for (k=0; k<length; k++)
755 if (Foam::mag(result(j,k)) >= bigValue)
757 bigValue = Foam::mag(result(j,k));
767 // We now have the pivot element
768 // Interchange rows if needed
771 for (j=0; j<length; j++)
773 Swap(result(iRow,j), result(iCol,j));
779 //Check for singularity
780 if (result(iCol, iCol) == 0.0)
782 FatalErrorIn("inline TensorN<Cmpt, length> inv(const TensorN<Cmpt, length>& t)")
783 << "Singular tensor" << length << Foam::abort(FatalError);
786 // Divide the pivot row by pivot element
787 pivotInv = pTraits<Cmpt>::one/result(iCol, iCol);
788 result(iCol, iCol) = pTraits<Cmpt>::one;
790 // Multiply all row elements by inverse
791 for (j=0; j<length; j++)
793 result(iCol,j) *= pivotInv;
797 for (j=0; j<length; j++)
802 result(j,iCol) = pTraits<Cmpt>::zero;
804 for (k=0; k<length; k++)
806 result(j,k) -= result(iCol,k)*temp;
812 // Unscamble the solution
813 for (i=length-1; i>=0; i--)
815 if (indexRow[i] != indexCol[i])
817 for (j=0; j<length; j++)
819 Swap(result(j,indexRow[i]), result(j,indexCol[i]));
828 //- Return the inverse of a tensor give the determinant
829 // Uses Gauss-Jordan Elimination with full pivoting
830 template <class Cmpt, int length>
831 inline TensorN<Cmpt, length> inv(const TensorN<Cmpt, length>& t)
833 TensorN<Cmpt, length> result(t);
835 label iRow=0, iCol=0;
836 Cmpt largestCoeff, temp;
837 Cmpt* __restrict__ srcIter;
838 Cmpt* __restrict__ destIter;
840 // Lists used for bookkeeping on the pivoting
841 List<label> indexCol(length), indexRow(length), iPivot(length);
845 // Main loop over columns to be reduced
846 for (int i=0; i<length; i++)
848 largestCoeff = pTraits<Cmpt>::zero;
850 //Search for pivot element
851 int curRowOffset = 0;
852 for (int j=0; j<length; j++)
856 for (int k=0; k<length; k++)
860 if ((temp = Foam::mag(result[curRowOffset+k])) >= largestCoeff)
869 curRowOffset += length;
873 // We now have the pivot element
874 // Interchange rows if needed
877 srcIter = &result(iRow,0);
878 destIter = &result(iCol,0);
880 for (int j=0; j<length; j++)
882 Swap((*srcIter), (*destIter));
890 //Check for singularity
891 srcIter = &result(iCol, iCol); //Dummy pointer to reduce indexing
892 if ((*srcIter) == Cmpt(0.0))
894 FatalErrorIn("inline TensorN<Cmpt, length> inv(const TensorN<Cmpt, length>& t)")
895 << "Singular tensor" << length << Foam::abort(FatalError);
898 // Divide the pivot row by pivot element
899 temp = pTraits<Cmpt>::one/(*srcIter);
900 (*srcIter) = pTraits<Cmpt>::one;
902 srcIter = &result(iCol,0);
903 for (int j=0; j<length; j++)
909 // Reduce the rows, excluding the pivot row
910 for (int j=0; j<length; j++)
914 destIter = &result(j,0);
915 srcIter = &result(iCol,0);
918 destIter[iCol] = pTraits<Cmpt>::zero;
920 for (int k=0; k<length; k++)
922 (*destIter) -= (*srcIter)*temp;
930 // Unscamble the solution
931 for (int i=length-1; i>=0; i--)
933 if (indexRow[i] != indexCol[i])
935 srcIter = &result[indexRow[i]];
936 destIter = &result[indexCol[i]];
937 for (int j=0; j<length; j++)
939 Swap((*srcIter), (*destIter));
950 //- Return tensor diagonal
951 template <class Cmpt, int length>
952 inline DiagTensorN<Cmpt, length> diag(const TensorN<Cmpt, length>& t)
957 //- Return tensor diagonal
958 template <class Cmpt, int length>
959 inline TensorN<Cmpt, length> negSumDiag(const TensorN<Cmpt, length>& t)
961 return t.negSumDiag();
964 //- Return the component sum
965 // template <class Cmpt, int length>
966 // inline Cmpt sum(const TensorN<Cmpt, length>& t)
968 // Cmpt result=Cmpt::zero;
969 // for(int i=0; i<TensorN<Cmpt, length>::nComponents; i++)
976 // * * * * * * * * * * * * * * * Global Operators * * * * * * * * * * * * * //
978 template<class Cmpt, int length>
979 class outerProduct<TensorN<Cmpt, length>, Cmpt>
983 typedef TensorN<Cmpt, length> type;
986 template<class Cmpt, int length>
987 class outerProduct<Cmpt, TensorN<Cmpt, length> >
991 typedef TensorN<Cmpt, length> type;
994 template<class Cmpt, int length>
995 class innerProduct<DiagTensorN<Cmpt, length>, TensorN<Cmpt, length> >
999 typedef TensorN<Cmpt, length> type;
1003 template<class Cmpt, int length>
1004 class innerProduct<TensorN<Cmpt, length>, DiagTensorN<Cmpt, length> >
1008 typedef TensorN<Cmpt, length> type;
1012 template<class Cmpt, int length>
1013 class innerProduct<SphericalTensorN<Cmpt, length>, TensorN<Cmpt, length> >
1017 typedef TensorN<Cmpt, length> type;
1021 template<class Cmpt, int length>
1022 class innerProduct<TensorN<Cmpt, length>, SphericalTensorN<Cmpt, length> >
1026 typedef TensorN<Cmpt, length> type;
1030 template<class Cmpt, int length>
1031 class innerProduct<VectorN<Cmpt, length>, TensorN<Cmpt, length> >
1035 typedef VectorN<Cmpt, length> type;
1039 template<class Cmpt, int length>
1040 class innerProduct<TensorN<Cmpt, length>, VectorN<Cmpt, length> >
1044 typedef VectorN<Cmpt, length> type;
1048 template<class Cmpt, int length>
1049 class innerProduct<TensorN<Cmpt, length>, TensorN<Cmpt, length> >
1053 typedef TensorN<Cmpt, length> type;
1057 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1059 } // End namespace Foam
1061 // ************************************************************************* //