StreeterFibreGenerator.cpp

00001 /*
00002 
00003 Copyright (c) 2005-2014, University of Oxford.
00004 All rights reserved.
00005 
00006 University of Oxford means the Chancellor, Masters and Scholars of the
00007 University of Oxford, having an administrative office at Wellington
00008 Square, Oxford OX1 2JD, UK.
00009 
00010 This file is part of Chaste.
00011 
00012 Redistribution and use in source and binary forms, with or without
00013 modification, are permitted provided that the following conditions are met:
00014  * Redistributions of source code must retain the above copyright notice,
00015    this list of conditions and the following disclaimer.
00016  * Redistributions in binary form must reproduce the above copyright notice,
00017    this list of conditions and the following disclaimer in the documentation
00018    and/or other materials provided with the distribution.
00019  * Neither the name of the University of Oxford nor the names of its
00020    contributors may be used to endorse or promote products derived from this
00021    software without specific prior written permission.
00022 
00023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00024 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00025 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00026 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
00027 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00028 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
00029 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00030 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
00032 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00033 
00034 */
00035 
00036 #include "UblasCustomFunctions.hpp"
00037 
00038 #include "StreeterFibreGenerator.hpp"
00039 
00040 #include <cmath>
00041 #include <fstream>
00042 #include <sstream>
00043 #include "OutputFileHandler.hpp"
00044 #include "Exception.hpp"
00045 //#include "HeartRegionCodes.hpp"
00046 
00047 template<unsigned SPACE_DIM>
00048 double StreeterFibreGenerator<SPACE_DIM>::GetAveragedThicknessLocalNode(
00049         const unsigned nodeIndex, const std::vector<double>& wallThickness) const
00050 {
00051     if (nodeIndex < this->mpMesh->GetDistributedVectorFactory()->GetLow() ||
00052         nodeIndex >= this->mpMesh->GetDistributedVectorFactory()->GetHigh() )
00053     {
00054         return 0.0;  //Don't calculate this for nodes which aren't local
00055     }
00056 
00057     // Initialise the average with the value corresponding to the current node
00058     double average = wallThickness[nodeIndex];
00059     unsigned nodes_visited = 1;
00060 
00061     // Use a set to store visited nodes
00062     std::set<unsigned> visited_nodes;
00063     visited_nodes.insert(nodeIndex);
00064 
00065     Node<SPACE_DIM>* p_current_node = this->mpMesh->GetNode(nodeIndex);
00066 
00067     // Loop over the elements containing the given node
00068     for(typename Node<SPACE_DIM>::ContainingElementIterator element_iterator = p_current_node->ContainingElementsBegin();
00069         element_iterator != p_current_node->ContainingElementsEnd();
00070         ++element_iterator)
00071     {
00072         // Get a pointer to the container element
00073         Element<SPACE_DIM,SPACE_DIM>* p_containing_element = this->mpMesh->GetElement(*element_iterator);
00074 
00075        // Loop over the nodes of the element
00076        for(unsigned node_local_index=0;
00077            node_local_index<p_containing_element->GetNumNodes();
00078            node_local_index++)
00079        {
00080             Node<SPACE_DIM>* p_neighbour_node = p_containing_element->GetNode(node_local_index);
00081             unsigned neighbour_node_index = p_neighbour_node->GetIndex();
00082 
00083             // Check if the neighbour node has already been visited
00084             if (visited_nodes.find(neighbour_node_index) == visited_nodes.end())
00085             {
00086                 average += wallThickness[neighbour_node_index];
00087                 visited_nodes.insert(neighbour_node_index);
00088                 nodes_visited++;
00089             }
00090        }
00091     }
00092 
00093     return average/nodes_visited;
00094 }
00095 
00096 
00097 
00098 
00099 template<unsigned SPACE_DIM>
00100 double StreeterFibreGenerator<SPACE_DIM>::GetFibreMaxAngle(
00101         const c_vector<HeartRegionType, SPACE_DIM+1>& nodesRegionsForElement) const
00102 {
00103     unsigned lv=0, rv=0;
00104 
00105     for (unsigned index=0; index<SPACE_DIM+1; index++)
00106     {
00107         switch (nodesRegionsForElement[index])
00108         {
00109             case HeartGeometryInformation<SPACE_DIM>::LEFT_VENTRICLE_SURFACE:
00110             case HeartGeometryInformation<SPACE_DIM>::LEFT_VENTRICLE_WALL:
00111             case HeartGeometryInformation<SPACE_DIM>::LEFT_SEPTUM:
00112                 lv++;
00113                 break;
00114 
00115             case HeartGeometryInformation<SPACE_DIM>::RIGHT_VENTRICLE_SURFACE:
00116             case HeartGeometryInformation<SPACE_DIM>::RIGHT_VENTRICLE_WALL:
00117             case HeartGeometryInformation<SPACE_DIM>::RIGHT_SEPTUM:
00118                 rv++;
00119                 break;
00120 
00121             case HeartGeometryInformation<SPACE_DIM>::UNKNOWN:
00122             default:
00123                 NEVER_REACHED;
00124         }
00125     }
00126 
00127     // If most of the nodes are in the right ventricle
00128     if (rv>lv)
00129     {
00130         return M_PI/4.0;
00131     }
00132 
00133     // Anywhere else
00134     return M_PI/3.0;
00135 }
00136 
00137 template<unsigned SPACE_DIM>
00138 StreeterFibreGenerator<SPACE_DIM>::StreeterFibreGenerator(AbstractTetrahedralMesh<SPACE_DIM,SPACE_DIM>& rMesh)
00139     : AbstractPerElementWriter<SPACE_DIM,SPACE_DIM,SPACE_DIM*SPACE_DIM>(&rMesh),
00140       mpGeometryInfo(NULL),
00141       mApexToBase(zero_vector<double>(SPACE_DIM)),
00142       mLogInfo(false)
00143 {
00144     mWallThickness.resize(rMesh.GetNumNodes());
00145     mAveragedWallThickness.resize(rMesh.GetNumNodes());
00146 }
00147 
00148 template<unsigned SPACE_DIM>
00149 StreeterFibreGenerator<SPACE_DIM>::~StreeterFibreGenerator()
00150 {
00151     delete mpGeometryInfo;
00152 }
00153 
00154 template<unsigned SPACE_DIM>
00155 void StreeterFibreGenerator<SPACE_DIM>::SetSurfaceFiles(
00156             const std::string &rEpicardiumFile,
00157             const std::string &rRightVentricleFile,
00158             const std::string &rLeftVentricleFile,
00159             bool indexFromZero)
00160 {
00161     // Compute the distance map of each surface
00162      mpGeometryInfo = new HeartGeometryInformation<SPACE_DIM>(*(this->mpMesh), rEpicardiumFile, rLeftVentricleFile, rRightVentricleFile, indexFromZero);
00163 }
00164 
00165 template<unsigned SPACE_DIM>
00166 void StreeterFibreGenerator<SPACE_DIM>::WriteHeaderOnMaster()
00167 {
00168     *(this->mpMasterFile) << this->mpMesh->GetNumElements();
00169     *(this->mpMasterFile) << std::setprecision(16);
00170 }
00171 
00172 template<unsigned SPACE_DIM>
00173 void StreeterFibreGenerator<SPACE_DIM>::PreWriteCalculations(OutputFileHandler& rOutputDirectory)
00174 {
00175     assert(SPACE_DIM == 3);
00176     if (mpGeometryInfo == NULL)
00177     {
00178         EXCEPTION("Files defining the heart surfaces not set");
00179     }
00180 
00181     // Open files
00182     out_stream p_regions_file, p_thickness_file, p_ave_thickness_file;
00183 
00184     //Make sure that only the master process writes the log files if requested.
00185     bool logInfo = PetscTools::AmMaster() & mLogInfo;
00186 
00187     if (logInfo)
00188     {
00189         p_regions_file  = rOutputDirectory.OpenOutputFile("node_regions.data");
00190         p_thickness_file = rOutputDirectory.OpenOutputFile("wall_thickness.data");
00191         p_ave_thickness_file = rOutputDirectory.OpenOutputFile("averaged_thickness.data");
00192     }
00193 
00194     //We expect that the apex to base has been set
00195     if (fabs(norm_2(mApexToBase)) < DBL_EPSILON)
00196     {
00197         EXCEPTION("Apex to base vector has not been set");
00198     }
00199 
00200     // Compute wall thickness parameter
00201     unsigned num_nodes = this->mpMesh->GetNumNodes();
00202     for (unsigned node_index=0; node_index<num_nodes; node_index++)
00203     {
00204         double dist_epi, dist_endo;
00205 
00206         HeartRegionType node_region = mpGeometryInfo->GetHeartRegion(node_index);
00207 
00208         switch(node_region)
00209         {
00210             case HeartGeometryInformation<SPACE_DIM>::LEFT_VENTRICLE_SURFACE:
00211             case HeartGeometryInformation<SPACE_DIM>::LEFT_VENTRICLE_WALL:
00212                 dist_epi = mpGeometryInfo->rGetDistanceMapEpicardium()[node_index];
00213                 dist_endo = mpGeometryInfo->rGetDistanceMapLeftVentricle()[node_index];
00214                 break;
00215 
00216             case HeartGeometryInformation<SPACE_DIM>::RIGHT_VENTRICLE_SURFACE:
00217             case HeartGeometryInformation<SPACE_DIM>::RIGHT_VENTRICLE_WALL:
00218                 dist_epi = mpGeometryInfo->rGetDistanceMapEpicardium()[node_index];
00219                 dist_endo = mpGeometryInfo->rGetDistanceMapRightVentricle()[node_index];
00220                 break;
00221 
00222             case HeartGeometryInformation<SPACE_DIM>::LEFT_SEPTUM:
00223                 dist_epi = mpGeometryInfo->rGetDistanceMapRightVentricle()[node_index];
00224                 dist_endo = mpGeometryInfo->rGetDistanceMapLeftVentricle()[node_index];
00225                 break;
00226 
00227             case HeartGeometryInformation<SPACE_DIM>::RIGHT_SEPTUM:
00228                 dist_epi = mpGeometryInfo->rGetDistanceMapLeftVentricle()[node_index];
00229                 dist_endo = mpGeometryInfo->rGetDistanceMapRightVentricle()[node_index];
00230                 break;
00231 
00232             case HeartGeometryInformation<SPACE_DIM>::UNKNOWN:
00233                 #define COVERAGE_IGNORE
00234                 std::cerr << "Wrong distances node: " << node_index << "\t"
00235                           << "Epi " << mpGeometryInfo->rGetDistanceMapEpicardium()[node_index] << "\t"
00236                           << "RV " << mpGeometryInfo->rGetDistanceMapRightVentricle()[node_index] << "\t"
00237                           << "LV " << mpGeometryInfo->rGetDistanceMapLeftVentricle()[node_index]
00238                           << std::endl;
00239 
00240                 // Make wall_thickness=0 as in Martin's code
00241                 dist_epi = 1;
00242                 dist_endo = 0;
00243                 break;
00244                 #undef COVERAGE_IGNORE
00245 
00246             default:
00247                 NEVER_REACHED;
00248         }
00249 
00250         mWallThickness[node_index] = dist_endo / (dist_endo + dist_epi);
00251 
00252         if (std::isnan(mWallThickness[node_index]))
00253         {
00254             #define COVERAGE_IGNORE
00255             /*
00256              *  A node contained on both epicardium and lv (or rv) surfaces has wall thickness 0/0.
00257              *  By setting its value to 0 we consider it contained only on the lv (or rv) surface.
00258              */
00259             mWallThickness[node_index] = 0;
00260             #undef COVERAGE_IGNORE
00261         }
00262 
00263         if (logInfo)
00264         {
00265             *p_regions_file << node_region*100 << "\n";
00266             *p_thickness_file << mWallThickness[node_index] << "\n";
00267         }
00268     }
00269 
00270     /*
00271      *  For each node, average its value of e with the values of all the neighbours
00272      */
00273     std::vector<double> my_averaged_wall_thickness(num_nodes);
00274     for (unsigned node_index=0; node_index<num_nodes; node_index++)
00275     {
00276         my_averaged_wall_thickness[node_index] = GetAveragedThicknessLocalNode(node_index, mWallThickness);
00277     }
00278 
00279     // Non-local information appear as zeros in the vector
00280     MPI_Allreduce(&my_averaged_wall_thickness[0], &mAveragedWallThickness[0], num_nodes,
00281                   MPI_DOUBLE, MPI_SUM, PETSC_COMM_WORLD);
00282 
00283     if (logInfo)
00284     {
00285         for (unsigned node_index=0; node_index<num_nodes; node_index++)
00286         {
00287              *p_ave_thickness_file << mAveragedWallThickness[node_index] << "\n";
00288         }
00289     }
00290 
00291     if (logInfo)
00292     {
00293         p_regions_file->close();
00294         p_thickness_file->close();
00295         p_ave_thickness_file->close();
00296     }
00297 }
00298 
00299 template<unsigned SPACE_DIM>
00300 void StreeterFibreGenerator<SPACE_DIM>::Visit(Element<SPACE_DIM, SPACE_DIM>* pElement,
00301                                               unsigned localElementIndex,
00302                                               c_vector<double, SPACE_DIM*SPACE_DIM>& rData)
00303 {
00304     /*
00305      *  The gradient of the averaged thickness at the element is:
00306      *
00307      *     grad_ave_wall_thickness[element_index] = ave' * BF * inv(J)
00308      *
00309      *  being : ave, averaged thickness values of the nodes defining the element
00310      *          J,   the Jacobian of the element as defined in class Element.
00311      *                               (-1 -1 -1)
00312      *          BF,  basis functions ( 1  0  0)
00313      *                               ( 0  1  0)
00314      *                               ( 0  0  1)
00315      *
00316      *  Defined as u in Streeter paper.
00317      */
00318     c_vector<double, SPACE_DIM> grad_ave_wall_thickness;
00319     c_vector<double, SPACE_DIM+1> elem_nodes_ave_thickness;
00320     double element_averaged_thickness = 0.0;
00321     c_vector<HeartRegionType, SPACE_DIM+1> elem_nodes_region;
00322 
00323     for (unsigned local_node_index=0; local_node_index<SPACE_DIM+1; local_node_index++)
00324     {
00325         // Get node's global index
00326         unsigned global_node_index = pElement->GetNode(local_node_index)->GetIndex();
00327 
00328         elem_nodes_ave_thickness[local_node_index] = mAveragedWallThickness[global_node_index];
00329         elem_nodes_region[local_node_index] = mpGeometryInfo->GetHeartRegion(global_node_index);
00330 
00331         // Calculate wall thickness averaged value for the element
00332         element_averaged_thickness +=  mWallThickness[global_node_index];
00333     }
00334 
00335     element_averaged_thickness /= SPACE_DIM+1;
00336 
00337     c_matrix<double, SPACE_DIM+1, SPACE_DIM> basis_functions( zero_matrix<double>(4u,3u) );
00338     basis_functions(0,0) = basis_functions(0,1) = basis_functions(0,2) = -1.0;
00339     basis_functions(1,0) = basis_functions(2,1) = basis_functions(3,2) =  1.0;
00340 
00341     c_matrix<double, SPACE_DIM+1, SPACE_DIM> temp;
00342     c_matrix<double, SPACE_DIM, SPACE_DIM> jacobian, inverse_jacobian;
00343     double jacobian_det;
00344     unsigned element_index = pElement->GetIndex();
00345     this->mpMesh->GetInverseJacobianForElement(element_index, jacobian, jacobian_det, inverse_jacobian);
00346     noalias(temp) = prod (basis_functions, inverse_jacobian);
00347     noalias(grad_ave_wall_thickness) = prod(elem_nodes_ave_thickness, temp);
00348 
00349     grad_ave_wall_thickness /= norm_2(grad_ave_wall_thickness);
00350 
00351     /*
00352      * Normal to the gradient (v in Streeter paper) which is then the circumferential direction
00353      * (it will be the fibre direction after rotation)
00354      *
00355      * Computed as the cross product with the base-apex direction (originally assumed base-apex axis is x). The output vector is not normal,
00356      * since the angle between them may be != 90, normalise it.
00357      */
00358     c_vector<double, SPACE_DIM> fibre_direction = VectorProduct(grad_ave_wall_thickness, mApexToBase);
00359     fibre_direction /= norm_2(fibre_direction);
00360 
00361     /*
00362      *  Longitude direction (w in Streeter paper)
00363      */
00364     c_vector<double, SPACE_DIM> longitude_direction = VectorProduct(grad_ave_wall_thickness, fibre_direction);
00365 
00366     /*
00367      *  Compute fibre to v angle: alpha = R*(1-2e)^3
00368      *
00369      *    R is the maximum angle between the fibre and the v axis (heart region dependant)
00370      *    (1 - 2e)^3 scales it by a value in [-1, 1] defining the rotation of the fibre based
00371      *       on the position in the wall
00372      */
00373     double alpha = GetFibreMaxAngle(elem_nodes_region) * SmallPow( (1 - 2*element_averaged_thickness), 3 );
00374 
00375     /*
00376      *  Apply alpha rotation about the u axis to the orthonormal basis
00377      *
00378      *               ( u(1) v(1) w(1) )
00379      *   (u, v, w) = ( u(2) v(2) w(2) )
00380      *               ( u(3) v(3) w(3) )
00381      *
00382      *  The following matrix defines a rotation about the u axis
00383      *
00384      *                 ( 1        0           0      ) (u')
00385      *   R = (u, v, w) ( 0    cos(alpha) -sin(alpha) ) (v')
00386      *                 ( 0    sin(alpha)  cos(alpha) ) (w')
00387      *
00388      *  The rotated basis is computed like:
00389      *
00390      *                                             ( 1        0           0      )
00391      *  (u, v_r, w_r ) = R * (u, v, w) = (u, v, w) ( 0    cos(alpha) -sin(alpha) )
00392      *                                             ( 0    sin(alpha)  cos(alpha) )
00393      *
00394      *  Which simplifies to:
00395      *
00396      *   v_r =  v*cos(alpha) + w*sin(alpha)
00397      *   w_r = -v*sin(alpha) + w*cos(alpha)
00398      */
00399     c_vector<double, SPACE_DIM> rotated_fibre_direction = fibre_direction*cos(alpha) + longitude_direction*sin(alpha);
00400     c_vector<double, SPACE_DIM> rotated_longitude_direction = -fibre_direction*sin(alpha) + longitude_direction*cos(alpha);
00401 
00402 
00403     /*
00404      * Test the orthonormality of the basis
00405      */
00406     assert( fabs(norm_2(rotated_fibre_direction) - 1) < 100*DBL_EPSILON );
00407     assert( fabs(norm_2(grad_ave_wall_thickness) - 1) < 100*DBL_EPSILON );
00408     assert( fabs(norm_2(rotated_longitude_direction) - 1) < 100*DBL_EPSILON );
00409 
00410     assert( fabs(inner_prod(rotated_fibre_direction, grad_ave_wall_thickness)) < 100*DBL_EPSILON );
00411     assert( fabs(inner_prod(rotated_fibre_direction, rotated_longitude_direction)) < 100*DBL_EPSILON);
00412     assert( fabs(inner_prod(grad_ave_wall_thickness, rotated_longitude_direction)) < 100*DBL_EPSILON);
00413 
00414     /*
00415      *  Output the direction of the myofibre, the transverse to it in the plane
00416      *  of the myocite laminae and the normal to this laminae (in that order)
00417      *
00418      *  See Fig. 1 "Laminar Structure of the Heart: a mathematical model" LeGrice et al. 97
00419      *
00420      */
00421     rData[0] = rotated_fibre_direction[0];
00422     rData[1] = rotated_fibre_direction[1];
00423     rData[2] = rotated_fibre_direction[2];
00424     rData[3] = grad_ave_wall_thickness[0];
00425     rData[4] = grad_ave_wall_thickness[1];
00426     rData[5] = grad_ave_wall_thickness[2];
00427     rData[6] = rotated_longitude_direction[0];
00428     rData[7] = rotated_longitude_direction[1];
00429     rData[8] = rotated_longitude_direction[2];
00430 }
00431 
00432 
00433 template<unsigned SPACE_DIM>
00434 void StreeterFibreGenerator<SPACE_DIM>::SetApexToBase(const c_vector<double, SPACE_DIM>& apexToBase)
00435 {
00436     double norm = norm_2(apexToBase);
00437     if (norm < DBL_EPSILON)
00438     {
00439         EXCEPTION("Apex to base vector should be non-zero");
00440     }
00441     mApexToBase = apexToBase / norm;
00442 }
00443 
00444 template<unsigned SPACE_DIM>
00445 void StreeterFibreGenerator<SPACE_DIM>::SetApexToBase(unsigned axis)
00446 {
00447     if (axis >= SPACE_DIM)
00448     {
00449         EXCEPTION("Apex to base coordinate axis was out of range");
00450     }
00451     mApexToBase = zero_vector<double>(SPACE_DIM);
00452     mApexToBase[axis] = 1.0;
00453 }
00454 
00455 template<unsigned SPACE_DIM>
00456 void StreeterFibreGenerator<SPACE_DIM>::SetLogInfo(bool logInfo)
00457 {
00458     mLogInfo = logInfo;
00459 }
00460 
00462 // Explicit instantiation
00464 #define COVERAGE_IGNORE
00465 template class StreeterFibreGenerator<3>;
00466 #undef COVERAGE_IGNORE

Generated by  doxygen 1.6.2