|
| 1 | +/* |
| 2 | +
|
| 3 | +Copyright (c) 2005-2025, University of Oxford. |
| 4 | +All rights reserved. |
| 5 | +
|
| 6 | +University of Oxford means the Chancellor, Masters and Scholars of the |
| 7 | +University of Oxford, having an administrative office at Wellington |
| 8 | +Square, Oxford OX1 2JD, UK. |
| 9 | +
|
| 10 | +This file is part of Chaste. |
| 11 | +
|
| 12 | +Redistribution and use in source and binary forms, with or without |
| 13 | +modification, are permitted provided that the following conditions are met: |
| 14 | + * Redistributions of source code must retain the above copyright notice, |
| 15 | + this list of conditions and the following disclaimer. |
| 16 | + * Redistributions in binary form must reproduce the above copyright notice, |
| 17 | + this list of conditions and the following disclaimer in the documentation |
| 18 | + and/or other materials provided with the distribution. |
| 19 | + * Neither the name of the University of Oxford nor the names of its |
| 20 | + contributors may be used to endorse or promote products derived from this |
| 21 | + software without specific prior written permission. |
| 22 | +
|
| 23 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 24 | +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 25 | +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 26 | +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| 27 | +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 28 | +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| 29 | +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 30 | +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 31 | +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| 32 | +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 33 | +
|
| 34 | +*/ |
| 35 | + |
| 36 | +#include "SimplifiedNagaiHondaForce.hpp" |
| 37 | + |
| 38 | +template<unsigned DIM> |
| 39 | +SimplifiedNagaiHondaForce<DIM>::SimplifiedNagaiHondaForce() |
| 40 | + : AbstractForce<DIM>(), |
| 41 | + mNagaiHondaDeformationEnergyParameter(100.0), // This is 1.0 in the Nagai & Honda paper. |
| 42 | + mNagaiHondaMembraneSurfaceEnergyParameter(10.0), // This is 0.1 in the Nagai & Honda paper. |
| 43 | + mNagaiHondaCellCellAdhesionEnergyParameter(0.5), // This corresponds to a value of 1.0 for |
| 44 | + // the sigma parameter in the Nagai & Honda |
| 45 | + // paper. In the paper, the sigma value is |
| 46 | + // set to 0.01. |
| 47 | + mNagaiHondaCellBoundaryAdhesionEnergyParameter(1.0), // This is 0.01 in the Nagai & Honda paper |
| 48 | + mNagaiHondaTargetAreaParameter(0.5*sqrt(3.0)), // This corresponds to a regular hexagon with edge length 0.5 |
| 49 | + mNagaiHondaTargetPerimeterParameter(3.0) |
| 50 | +{ |
| 51 | +} |
| 52 | + |
| 53 | +template<unsigned DIM> |
| 54 | +SimplifiedNagaiHondaForce<DIM>::~SimplifiedNagaiHondaForce() |
| 55 | +{ |
| 56 | +} |
| 57 | + |
| 58 | +template<unsigned DIM> |
| 59 | +void SimplifiedNagaiHondaForce<DIM>::AddForceContribution(AbstractCellPopulation<DIM>& rCellPopulation) |
| 60 | +{ |
| 61 | + // Throw an exception message if not using a VertexBasedCellPopulation |
| 62 | + if (dynamic_cast<VertexBasedCellPopulation<DIM>*>(&rCellPopulation) == nullptr) |
| 63 | + { |
| 64 | + EXCEPTION("SimplifiedNagaiHondaForce is to be used with a VertexBasedCellPopulation only"); |
| 65 | + } |
| 66 | + |
| 67 | + // Define some helper variables |
| 68 | + VertexBasedCellPopulation<DIM>* p_cell_population = static_cast<VertexBasedCellPopulation<DIM>*>(&rCellPopulation); |
| 69 | + unsigned num_nodes = p_cell_population->GetNumNodes(); |
| 70 | + unsigned num_elements = p_cell_population->GetNumElements(); |
| 71 | + |
| 72 | + /* |
| 73 | + * Check if a subclass of AbstractTargetAreaModifier is being used by |
| 74 | + * interrogating the first cell (assuming either all cells, or no cells, in |
| 75 | + * the population have the CellData item "target area"). |
| 76 | + */ |
| 77 | + bool using_target_area_modifier = p_cell_population->Begin()->GetCellData()->HasItem("target area"); |
| 78 | + |
| 79 | + if (using_target_area_modifier) |
| 80 | + { |
| 81 | + // This simple model does not support a target area modifier |
| 82 | + NEVER_REACHED; |
| 83 | + } |
| 84 | + |
| 85 | + // Begin by computing the area and perimeter of each element in the mesh, to avoid having to do this multiple times |
| 86 | + std::vector<double> element_areas(num_elements); |
| 87 | + std::vector<double> element_perimeters(num_elements); |
| 88 | + std::vector<double> target_areas(num_elements); |
| 89 | + std::vector<double> target_perimeters(num_elements); |
| 90 | + |
| 91 | + for (typename VertexMesh<DIM,DIM>::VertexElementIterator elem_iter = p_cell_population->rGetMesh().GetElementIteratorBegin(); |
| 92 | + elem_iter != p_cell_population->rGetMesh().GetElementIteratorEnd(); |
| 93 | + ++elem_iter) |
| 94 | + { |
| 95 | + unsigned elem_index = elem_iter->GetIndex(); |
| 96 | + element_areas[elem_index] = p_cell_population->rGetMesh().GetVolumeOfElement(elem_index); |
| 97 | + element_perimeters[elem_index] = p_cell_population->rGetMesh().GetSurfaceAreaOfElement(elem_index); |
| 98 | + |
| 99 | + target_areas[elem_index] = mNagaiHondaTargetAreaParameter; |
| 100 | + target_perimeters[elem_index] = mNagaiHondaTargetPerimeterParameter; |
| 101 | + } |
| 102 | + |
| 103 | + // Iterate over vertices in the cell population |
| 104 | + for (unsigned node_index=0; node_index<num_nodes; node_index++) |
| 105 | + { |
| 106 | + Node<DIM>* p_this_node = p_cell_population->GetNode(node_index); |
| 107 | + |
| 108 | + /* |
| 109 | + * The force on this Node is given by the gradient of the total free |
| 110 | + * energy of the CellPopulation, evaluated at the position of the vertex. This |
| 111 | + * free energy is the sum of the free energies of all CellPtrs in |
| 112 | + * the cell population. The free energy of each CellPtr is comprised of three |
| 113 | + * parts - a cell deformation energy, a membrane surface tension energy |
| 114 | + * and an adhesion energy. |
| 115 | + * |
| 116 | + * Note that since the movement of this Node only affects the free energy |
| 117 | + * of the CellPtrs containing it, we can just consider the contributions |
| 118 | + * to the free energy gradient from each of these CellPtrs. |
| 119 | + */ |
| 120 | + c_vector<double, DIM> deformation_contribution = zero_vector<double>(DIM); |
| 121 | + c_vector<double, DIM> membrane_surface_tension_contribution = zero_vector<double>(DIM); |
| 122 | + c_vector<double, DIM> adhesion_contribution = zero_vector<double>(DIM); |
| 123 | + |
| 124 | + // Find the indices of the elements owned by this node |
| 125 | + std::set<unsigned> containing_elem_indices = p_cell_population->GetNode(node_index)->rGetContainingElementIndices(); |
| 126 | + |
| 127 | + // Iterate over these elements |
| 128 | + for (std::set<unsigned>::iterator iter = containing_elem_indices.begin(); |
| 129 | + iter != containing_elem_indices.end(); |
| 130 | + ++iter) |
| 131 | + { |
| 132 | + // Get this element, its index and its number of nodes |
| 133 | + VertexElement<DIM, DIM>* p_element = p_cell_population->GetElement(*iter); |
| 134 | + unsigned elem_index = p_element->GetIndex(); |
| 135 | + unsigned num_nodes_elem = p_element->GetNumNodes(); |
| 136 | + |
| 137 | + // Find the local index of this node in this element |
| 138 | + unsigned local_index = p_element->GetNodeLocalIndex(node_index); |
| 139 | + |
| 140 | + // Add the force contribution from this cell's deformation energy (note the minus sign) |
| 141 | + c_vector<double, DIM> element_area_gradient = p_cell_population->rGetMesh().GetAreaGradientOfElementAtNode(p_element, local_index); |
| 142 | + deformation_contribution -= 2*GetNagaiHondaDeformationEnergyParameter()*(element_areas[elem_index] - target_areas[elem_index])*element_area_gradient; |
| 143 | + |
| 144 | + // Get the previous and next nodes in this element |
| 145 | + unsigned previous_node_local_index = (num_nodes_elem+local_index-1)%num_nodes_elem; |
| 146 | + Node<DIM>* p_previous_node = p_element->GetNode(previous_node_local_index); |
| 147 | + |
| 148 | + unsigned next_node_local_index = (local_index+1)%num_nodes_elem; |
| 149 | + Node<DIM>* p_next_node = p_element->GetNode(next_node_local_index); |
| 150 | + |
| 151 | + // Compute the adhesion parameter for each of these edges |
| 152 | + double previous_edge_adhesion_parameter = GetAdhesionParameter(p_previous_node, p_this_node, *p_cell_population); |
| 153 | + double next_edge_adhesion_parameter = GetAdhesionParameter(p_this_node, p_next_node, *p_cell_population); |
| 154 | + |
| 155 | + // Compute the gradient of each these edges, computed at the present node |
| 156 | + c_vector<double, DIM> previous_edge_gradient = -p_cell_population->rGetMesh().GetNextEdgeGradientOfElementAtNode(p_element, previous_node_local_index); |
| 157 | + c_vector<double, DIM> next_edge_gradient = p_cell_population->rGetMesh().GetNextEdgeGradientOfElementAtNode(p_element, local_index); |
| 158 | + |
| 159 | + // Add the force contribution from cell-cell and cell-boundary adhesion (note the minus sign) |
| 160 | + adhesion_contribution -= previous_edge_adhesion_parameter*previous_edge_gradient + next_edge_adhesion_parameter*next_edge_gradient; |
| 161 | + |
| 162 | + // Add the force contribution from this cell's membrane surface tension (note the minus sign) |
| 163 | + c_vector<double, DIM> element_perimeter_gradient; |
| 164 | + element_perimeter_gradient = previous_edge_gradient + next_edge_gradient; |
| 165 | + //double cell_target_perimeter = 2*sqrt(M_PI*target_areas[elem_index]); |
| 166 | + membrane_surface_tension_contribution -= 2*GetNagaiHondaMembraneSurfaceEnergyParameter()*(element_perimeters[elem_index] - target_perimeters[elem_index])*element_perimeter_gradient; |
| 167 | + } |
| 168 | + |
| 169 | + c_vector<double, DIM> force_on_node = deformation_contribution + membrane_surface_tension_contribution + adhesion_contribution; |
| 170 | + p_cell_population->GetNode(node_index)->AddAppliedForceContribution(force_on_node); |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +template<unsigned DIM> |
| 175 | +double SimplifiedNagaiHondaForce<DIM>::GetAdhesionParameter(Node<DIM>* pNodeA, Node<DIM>* pNodeB, VertexBasedCellPopulation<DIM>& rVertexCellPopulation) |
| 176 | +{ |
| 177 | + // Find the indices of the elements owned by each node |
| 178 | + std::set<unsigned> elements_containing_nodeA = pNodeA->rGetContainingElementIndices(); |
| 179 | + std::set<unsigned> elements_containing_nodeB = pNodeB->rGetContainingElementIndices(); |
| 180 | + |
| 181 | + // Find common elements |
| 182 | + std::set<unsigned> shared_elements; |
| 183 | + std::set_intersection(elements_containing_nodeA.begin(), |
| 184 | + elements_containing_nodeA.end(), |
| 185 | + elements_containing_nodeB.begin(), |
| 186 | + elements_containing_nodeB.end(), |
| 187 | + std::inserter(shared_elements, shared_elements.begin())); |
| 188 | + |
| 189 | + // Check that the nodes have a common edge |
| 190 | + assert(!shared_elements.empty()); |
| 191 | + |
| 192 | + double adhesion_parameter = GetNagaiHondaCellCellAdhesionEnergyParameter(); |
| 193 | + |
| 194 | + // If the edge corresponds to a single element, then the cell is on the boundary |
| 195 | + if (shared_elements.size() == 1) |
| 196 | + { |
| 197 | + adhesion_parameter = GetNagaiHondaCellBoundaryAdhesionEnergyParameter(); |
| 198 | + } |
| 199 | + |
| 200 | + return adhesion_parameter; |
| 201 | +} |
| 202 | + |
| 203 | +template<unsigned DIM> |
| 204 | +double SimplifiedNagaiHondaForce<DIM>::GetNagaiHondaDeformationEnergyParameter() |
| 205 | +{ |
| 206 | + return mNagaiHondaDeformationEnergyParameter; |
| 207 | +} |
| 208 | + |
| 209 | +template<unsigned DIM> |
| 210 | +double SimplifiedNagaiHondaForce<DIM>::GetNagaiHondaMembraneSurfaceEnergyParameter() |
| 211 | +{ |
| 212 | + return mNagaiHondaMembraneSurfaceEnergyParameter; |
| 213 | +} |
| 214 | + |
| 215 | +template<unsigned DIM> |
| 216 | +double SimplifiedNagaiHondaForce<DIM>::GetNagaiHondaCellCellAdhesionEnergyParameter() |
| 217 | +{ |
| 218 | + return mNagaiHondaCellCellAdhesionEnergyParameter; |
| 219 | +} |
| 220 | + |
| 221 | +template<unsigned DIM> |
| 222 | +double SimplifiedNagaiHondaForce<DIM>::GetNagaiHondaCellBoundaryAdhesionEnergyParameter() |
| 223 | +{ |
| 224 | + return mNagaiHondaCellBoundaryAdhesionEnergyParameter; |
| 225 | +} |
| 226 | + |
| 227 | +template<unsigned DIM> |
| 228 | +double SimplifiedNagaiHondaForce<DIM>::GetNagaiHondaTargetAreaParameter() |
| 229 | +{ |
| 230 | + return mNagaiHondaTargetAreaParameter; |
| 231 | +} |
| 232 | + |
| 233 | +template<unsigned DIM> |
| 234 | +void SimplifiedNagaiHondaForce<DIM>::SetNagaiHondaDeformationEnergyParameter(double deformationEnergyParameter) |
| 235 | +{ |
| 236 | + mNagaiHondaDeformationEnergyParameter = deformationEnergyParameter; |
| 237 | +} |
| 238 | + |
| 239 | +template<unsigned DIM> |
| 240 | +void SimplifiedNagaiHondaForce<DIM>::SetNagaiHondaMembraneSurfaceEnergyParameter(double membraneSurfaceEnergyParameter) |
| 241 | +{ |
| 242 | + mNagaiHondaMembraneSurfaceEnergyParameter = membraneSurfaceEnergyParameter; |
| 243 | +} |
| 244 | + |
| 245 | +template<unsigned DIM> |
| 246 | +void SimplifiedNagaiHondaForce<DIM>::SetNagaiHondaCellCellAdhesionEnergyParameter(double cellCellAdhesionEnergyParameter) |
| 247 | +{ |
| 248 | + mNagaiHondaCellCellAdhesionEnergyParameter = cellCellAdhesionEnergyParameter; |
| 249 | +} |
| 250 | + |
| 251 | +template<unsigned DIM> |
| 252 | +void SimplifiedNagaiHondaForce<DIM>::SetNagaiHondaCellBoundaryAdhesionEnergyParameter(double cellBoundaryAdhesionEnergyParameter) |
| 253 | +{ |
| 254 | + mNagaiHondaCellBoundaryAdhesionEnergyParameter = cellBoundaryAdhesionEnergyParameter; |
| 255 | +} |
| 256 | + |
| 257 | +template<unsigned DIM> |
| 258 | +void SimplifiedNagaiHondaForce<DIM>::SetNagaiHondaTargetAreaParameter(double nagaiHondaTargetAreaParameter) |
| 259 | +{ |
| 260 | + mNagaiHondaTargetAreaParameter = nagaiHondaTargetAreaParameter; |
| 261 | +} |
| 262 | + |
| 263 | +template<unsigned DIM> |
| 264 | +void SimplifiedNagaiHondaForce<DIM>::SetNagaiHondaTargetPerimeterParameter(double nagaiHondaTargetPerimeterParameter) |
| 265 | +{ |
| 266 | + mNagaiHondaTargetPerimeterParameter = nagaiHondaTargetPerimeterParameter; |
| 267 | +} |
| 268 | + |
| 269 | +template<unsigned DIM> |
| 270 | +void SimplifiedNagaiHondaForce<DIM>::OutputForceParameters(out_stream& rParamsFile) |
| 271 | +{ |
| 272 | + *rParamsFile << "\t\t\t<NagaiHondaDeformationEnergyParameter>" << mNagaiHondaDeformationEnergyParameter << "</NagaiHondaDeformationEnergyParameter>\n"; |
| 273 | + *rParamsFile << "\t\t\t<NagaiHondaMembraneSurfaceEnergyParameter>" << mNagaiHondaMembraneSurfaceEnergyParameter << "</NagaiHondaMembraneSurfaceEnergyParameter>\n"; |
| 274 | + *rParamsFile << "\t\t\t<NagaiHondaCellCellAdhesionEnergyParameter>" << mNagaiHondaCellCellAdhesionEnergyParameter << "</NagaiHondaCellCellAdhesionEnergyParameter>\n"; |
| 275 | + *rParamsFile << "\t\t\t<NagaiHondaCellBoundaryAdhesionEnergyParameter>" << mNagaiHondaCellBoundaryAdhesionEnergyParameter << "</NagaiHondaCellBoundaryAdhesionEnergyParameter>\n"; |
| 276 | + *rParamsFile << "\t\t\t<NagaiHondaTargetAreaParameter>" << mNagaiHondaTargetAreaParameter << "</NagaiHondaTargetAreaParameter>\n"; |
| 277 | + *rParamsFile << "\t\t\t<NagaiHondaTargetPerimeterParameter>" << mNagaiHondaTargetPerimeterParameter << "</NagaiHondaTargetPerimeterParameter>\n"; |
| 278 | + |
| 279 | + // Call method on direct parent class |
| 280 | + AbstractForce<DIM>::OutputForceParameters(rParamsFile); |
| 281 | +} |
| 282 | + |
| 283 | +// Explicit instantiation |
| 284 | +template class SimplifiedNagaiHondaForce<1>; |
| 285 | +template class SimplifiedNagaiHondaForce<2>; |
| 286 | +template class SimplifiedNagaiHondaForce<3>; |
| 287 | + |
| 288 | +// Serialization for Boost >= 1.36 |
| 289 | +#include "SerializationExportWrapperForCpp.hpp" |
| 290 | +EXPORT_TEMPLATE_CLASS_SAME_DIMS(SimplifiedNagaiHondaForce) |
0 commit comments