Chaste Commit::baa90ac2819b962188b7562f2326be23c47859a7
ImmersedBoundaryMesh.hpp
1/*
2
3Copyright (c) 2005-2024, University of Oxford.
4All rights reserved.
5
6University of Oxford means the Chancellor, Masters and Scholars of the
7University of Oxford, having an administrative office at Wellington
8Square, Oxford OX1 2JD, UK.
9
10This file is part of Chaste.
11
12Redistribution and use in source and binary forms, with or without
13modification, 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
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34*/
35
36#ifndef IMMERSEDBOUNDARYMESH_HPP_
37#define IMMERSEDBOUNDARYMESH_HPP_
38
39// Forward declaration prevents circular include chain
40template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
42
43#include <set>
44#include <vector>
45
46#include <boost/polygon/voronoi.hpp>
47#include <boost/serialization/base_object.hpp>
48#include <boost/serialization/split_member.hpp>
49#include <boost/serialization/vector.hpp>
50
51#include "AbstractMesh.hpp"
52#include "ArchiveLocationInfo.hpp"
54#include "FluidSource.hpp"
55#include "ImmersedBoundaryArray.hpp"
56#include "ImmersedBoundaryElement.hpp"
57#include "ImmersedBoundaryMeshReader.hpp"
58#include "ImmersedBoundaryMeshWriter.hpp"
59#include "Node.hpp"
60
61
68template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
69class ImmersedBoundaryMesh : public AbstractMesh<ELEMENT_DIM, SPACE_DIM>
70{
71 friend class TestImmersedBoundaryMesh;
72
73protected:
75 unsigned mNumGridPtsX;
76
78 unsigned mNumGridPtsY;
79
82
85
88
94
97
101 static constexpr double mVoronoiHalo = 0.1;
102
104 std::vector<unsigned> mDeletedNodeIndices;
105
107 std::vector<unsigned> mDeletedElementIndices;
108
110 multi_array<double, 3> m2dVelocityGrids;
111
113 multi_array<double, 4> m3dVelocityGrids;
114
116 std::vector<ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>*> mElements;
117
119 std::vector<ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>*> mLaminas;
120
122 std::vector<std::shared_ptr<FluidSource<SPACE_DIM>>> mElementFluidSources;
123
125 std::vector<std::shared_ptr<FluidSource<SPACE_DIM>>> mBalancingFluidSources;
126
128 boost::polygon::voronoi_diagram<double> mNodeLocationsVoronoiDiagram;
129
138
146 unsigned SolveNodeMapping(unsigned index) const;
147
155 unsigned SolveElementMapping(unsigned index) const;
156
164 unsigned SolveBoundaryElementMapping(unsigned index) const;
165
174 void TagBoundaryElements();
175
188 unsigned nodeAIndex,
189 unsigned nodeBIndex,
190 c_vector<double, SPACE_DIM> centroid,
191 c_vector<double, SPACE_DIM> axisOfDivision);
192
195
203 template <class Archive>
204 void save(Archive& archive, const unsigned int version) const
205 {
206 archive& boost::serialization::base_object<AbstractMesh<ELEMENT_DIM, SPACE_DIM> >(*this);
207
208 // Create a mesh writer pointing to the correct file and directory
211 false);
212 mesh_writer.WriteFilesUsingMesh(*(const_cast<ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>*>(this)));
213 }
214
221 template <class Archive>
222 void load(Archive& archive, const unsigned int version)
223 {
224 archive& boost::serialization::base_object<AbstractMesh<ELEMENT_DIM, SPACE_DIM> >(*this);
225
227 this->ConstructFromMeshReader(mesh_reader);
228 }
229 BOOST_SERIALIZATION_SPLIT_MEMBER()
230
231public:
233 class ImmersedBoundaryElementIterator;
234
236 class ImmersedBoundaryLaminaIterator;
237
241 const std::vector<Node<SPACE_DIM>*>& rGetNodes() const;
242
248 inline ImmersedBoundaryElementIterator GetElementIteratorBegin(bool skipDeletedElements = true);
249
253 inline ImmersedBoundaryElementIterator GetElementIteratorEnd();
254
260 inline ImmersedBoundaryLaminaIterator GetLaminaIteratorBegin(bool skipDeletedLaminas = true);
261
265 inline ImmersedBoundaryLaminaIterator GetLaminaIteratorEnd();
266
276 ImmersedBoundaryMesh(std::vector<Node<SPACE_DIM>*> nodes,
277 std::vector<ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>*> elements,
278 std::vector<ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>*> laminas = {},
279 unsigned numGridPtsX = 128u,
280 unsigned numGridPtsY = 128u);
281
286
290 virtual ~ImmersedBoundaryMesh();
291
295 virtual unsigned GetNumNodes() const;
296
300 virtual unsigned GetNumElements() const;
301
305 unsigned GetNumAllElements() const;
306
310 unsigned GetNumLaminas() const;
311
315 unsigned GetNumGridPtsX() const;
316
320 unsigned GetNumGridPtsY() const;
321
325 double GetCharacteristicNodeSpacing() const;
326
330 double GetSpacingRatio() const;
331
335 unsigned GetMaxNodeIndex() const;
336
340 unsigned GetMaxElementIndex() const;
341
345 unsigned GetMaxLaminaIndex() const;
346
357 c_vector<double, SPACE_DIM> GetVectorFromAtoB(const c_vector<double, SPACE_DIM>& rLocation1, const c_vector<double, SPACE_DIM>& rLocation2);
358
365 void SetNode(unsigned nodeIndex, ChastePoint<SPACE_DIM> point);
366
374 void ConformToGeometry(c_vector<double, SPACE_DIM>& rLocation);
375
379 const multi_array<double, 3>& rGet2dVelocityGrids() const;
380
384 //const multi_array<double, 4>& rGet3dVelocityGrids() const;
385
389 multi_array<double, 3>& rGetModifiable2dVelocityGrids();
390
394 void SetNumGridPtsX(unsigned meshPointsX);
395
399 void SetNumGridPtsY(unsigned meshPointsY);
400
404 void SetNumGridPtsXAndY(unsigned numGridPts);
405
409 void SetCharacteristicNodeSpacing(double nodeSpacing);
410
416 unsigned AddNode(Node<SPACE_DIM>* pNewNode);
417
421 std::vector<std::shared_ptr<FluidSource<SPACE_DIM>>>& rGetElementFluidSources();
422
426 std::vector<std::shared_ptr<FluidSource<SPACE_DIM>>>& rGetBalancingFluidSources();
427
434 std::set<unsigned> GetNeighbouringNodeIndices(unsigned nodeIndex);
435
442
448 ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>* GetLamina(unsigned index) const;
449
465 virtual c_vector<double, SPACE_DIM> GetCentroidOfElement(unsigned index);
466
473
477 virtual void Clear();
478
488 virtual double GetVolumeOfElement(unsigned index);
489
499 virtual double GetSurfaceAreaOfElement(unsigned index);
500
509 double GetVoronoiSurfaceAreaOfElement(unsigned elemIdx);
510
518 double GetAverageNodeSpacingOfElement(unsigned index, bool recalculate = true);
519
527 double GetAverageNodeSpacingOfLamina(unsigned index, bool recalculate = true);
528
558 virtual c_vector<double, 3> CalculateMomentsOfElement(unsigned index);
559
570 double GetElongationShapeFactorOfElement(unsigned elementIndex);
571
578 double GetTortuosityOfMesh();
579
590 double GetSkewnessOfElementMassDistributionAboutAxis(unsigned elemIndex, c_vector<double, SPACE_DIM> axis);
591
599
625 c_vector<double, SPACE_DIM> GetShortAxisOfElement(unsigned index);
626
641 c_vector<double, SPACE_DIM> axisOfDivision,
642 bool placeOriginalElementBelow = false);
643
653 bool placeOriginalElementBelow = false);
654
659
663 void SetElementDivisionSpacing(double elementDivisionSpacing);
664
668 double GetNeighbourDist() const;
669
673 void SetCellRearrangementThreshold(double cellRearrangementThreshold);
674
679
683 void SetNeighbourDist(double neighbourDist);
684
690
696 void ReMesh(bool randomOrder=false);
697
704 void ReMeshElement(ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* pElement, bool randomOrder);
705
713
722
729 std::set<unsigned> GetNeighbouringElementIndices(unsigned elemIdx);
730
737 double CalculateLengthOfVoronoiEdge(const boost::polygon::voronoi_diagram<double>::edge_type& rEdge);
738
747 std::array<unsigned, 13> GetPolygonDistribution();
748
754 const boost::polygon::voronoi_diagram<double>& rGetNodeLocationsVoronoiDiagram(bool update=true);
755
757 const std::vector<unsigned int>& GetVoronoiCellIdsIndexedByNodeIndex() const;
758
765 int ScaleUpToVoronoiCoordinate(double location) const;
766
773 double ScaleDistanceDownFromVoronoi(const double distance) const;
774
775
780 {
781 public:
788
794
801
806 inline ImmersedBoundaryElementIterator& operator++();
807
818 ImmersedBoundaryElementIterator(ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>& rMesh,
819 typename std::vector<ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>*>::iterator elementIter,
820 bool skipDeletedElements = true);
821
822 private:
825
827 typename std::vector<ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>*>::iterator mElementIter;
828
831
836 inline bool IsAtEnd();
837
842 inline bool IsAllowedElement();
843 };
844
849 {
850 public:
856 inline ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>& operator*();
857
862 inline ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>* operator->();
863
870
875 inline ImmersedBoundaryLaminaIterator& operator++();
876
887 ImmersedBoundaryLaminaIterator(ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>& rMesh,
888 typename std::vector<ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>*>::iterator laminaIter,
889 bool skipDeletedLaminas = true);
890
891 private:
894
896 typename std::vector<ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>*>::iterator mLaminaIter;
897
900
905 inline bool IsAtEnd();
906
911 inline bool IsAllowedLamina();
912 };
913};
914
917
918
919// ImmersedBoundaryElementIterator class implementation - most methods are inlined //
921
922template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
928
929template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
934
935template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
941
942template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
948
949template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
954
955template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
957{
958 do
959 {
960 ++mElementIter;
961 } while (!IsAtEnd() && !IsAllowedElement());
962
963 return (*this);
964}
965
966template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
969 typename std::vector<ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>*>::iterator elementIter,
970 bool skipDeletedElements)
971 : mrMesh(rMesh),
972 mElementIter(elementIter),
973 mSkipDeletedElements(skipDeletedElements)
974{
975 if (mrMesh.mElements.empty())
976 {
977 // Cope with empty meshes
979 }
980 else
981 {
982 // Make sure we start at an allowed element
983 if (mElementIter == mrMesh.mElements.begin() && !IsAllowedElement())
984 {
985 ++(*this);
986 }
987 }
988}
989
990template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
992{
993 return mElementIter == mrMesh.mElements.end();
994}
995
996template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
998{
999 return !(mSkipDeletedElements && (*this)->IsDeleted());
1000}
1001
1003// ImmersedBoundaryLaminaIterator class implementation - most methods are inlined //
1005
1006template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1012
1013template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1018
1019template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1021{
1022 assert(!IsAtEnd());
1023 return **mLaminaIter;
1024}
1025
1026template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1028{
1029 assert(!IsAtEnd());
1030 return *mLaminaIter;
1031}
1032
1033template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1038
1039template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1041{
1042 do
1043 {
1044 ++mLaminaIter;
1045 } while (!IsAtEnd() && !IsAllowedLamina());
1046
1047 return (*this);
1048}
1049
1050template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1053 typename std::vector<ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>*>::iterator laminaIter,
1054 bool skipDeletedLaminas)
1055 : mrMesh(rMesh),
1056 mLaminaIter(laminaIter),
1057 mSkipDeletedLaminas(skipDeletedLaminas)
1058{
1059 if (mrMesh.mLaminas.empty())
1060 {
1061 // Cope with empty meshes
1062 mLaminaIter = mrMesh.mLaminas.end();
1063 }
1064 else
1065 {
1066 // Make sure we start at an allowed lamina
1067 if (mLaminaIter == mrMesh.mLaminas.begin() && !IsAllowedLamina())
1068 {
1069 ++(*this);
1070 }
1071 }
1072}
1073
1074template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1076{
1077 return mLaminaIter == mrMesh.mLaminas.end();
1078}
1079
1080template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1082{
1083 return !(mSkipDeletedLaminas && (*this)->IsDeleted());
1084}
1085
1086#endif /*IMMERSEDBOUNDARYMESH_HPP_*/
gcov doesn't like this file...
#define EXPORT_TEMPLATE_CLASS_ALL_DIMS(CLASS)
static std::string GetMeshFilename()
static std::string GetArchiveDirectory()
static std::string GetArchiveRelativePath()
void WriteFilesUsingMesh(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh)
std::vector< ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * >::iterator mElementIter
ImmersedBoundaryElementIterator(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, typename std::vector< ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * >::iterator elementIter, bool skipDeletedElements=true)
ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > & operator*()
ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * operator->()
bool operator!=(const typename ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM >::ImmersedBoundaryElementIterator &rOther)
bool operator!=(const typename ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM >::ImmersedBoundaryLaminaIterator &rOther)
ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > & operator*()
ImmersedBoundaryLaminaIterator(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, typename std::vector< ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > * >::iterator laminaIter, bool skipDeletedLaminas=true)
std::vector< ImmersedBoundaryElement< ELEMENT_DIM-1, SPACE_DIM > * >::iterator mLaminaIter
ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > * operator->()
ChasteCuboid< SPACE_DIM > CalculateBoundingBoxOfElement(unsigned index)
std::vector< ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * > mElements
void ReMesh(bool randomOrder=false)
virtual double GetVolumeOfElement(unsigned index)
double GetCharacteristicNodeSpacing() const
void SetNeighbourDist(double neighbourDist)
bool NodesInDifferentElementOrLamina(Node< SPACE_DIM > *pNodeA, Node< SPACE_DIM > *pNodeB)
unsigned DivideElement(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, unsigned nodeAIndex, unsigned nodeBIndex, c_vector< double, SPACE_DIM > centroid, c_vector< double, SPACE_DIM > axisOfDivision)
const std::vector< Node< SPACE_DIM > * > & rGetNodes() const
unsigned GetMaxElementIndex() const
std::vector< unsigned > mVoronoiCellIdsIndexedByNodeIndex
multi_array< double, 3 > & rGetModifiable2dVelocityGrids()
void save(Archive &archive, const unsigned int version) const
std::vector< unsigned > mDeletedNodeIndices
unsigned SolveElementMapping(unsigned index) const
ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > * GetLamina(unsigned index) const
void load(Archive &archive, const unsigned int version)
void ReMeshLamina(ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > *pLamina, bool randomOrder)
void SetNode(unsigned nodeIndex, ChastePoint< SPACE_DIM > point)
ImmersedBoundaryElementIterator GetElementIteratorEnd()
ImmersedBoundaryLaminaIterator GetLaminaIteratorBegin(bool skipDeletedLaminas=true)
double GetSkewnessOfElementMassDistributionAboutAxis(unsigned elemIndex, c_vector< double, SPACE_DIM > axis)
ImmersedBoundaryElementIterator GetElementIteratorBegin(bool skipDeletedElements=true)
multi_array< double, 4 > m3dVelocityGrids
static constexpr double mVoronoiHalo
void SetNumGridPtsXAndY(unsigned numGridPts)
double ScaleDistanceDownFromVoronoi(const double distance) const
void SetCharacteristicNodeSpacing(double nodeSpacing)
ImmersedBoundaryLaminaIterator GetLaminaIteratorEnd()
std::array< unsigned, 13 > GetPolygonDistribution()
double CalculateLengthOfVoronoiEdge(const boost::polygon::voronoi_diagram< double >::edge_type &rEdge)
void SetCellRearrangementThreshold(double cellRearrangementThreshold)
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > mElementFluidSources
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > & rGetElementFluidSources()
virtual unsigned GetNumNodes() const
std::vector< unsigned > mDeletedElementIndices
double GetElongationShapeFactorOfElement(unsigned elementIndex)
unsigned SolveNodeMapping(unsigned index) const
boost::polygon::voronoi_diagram< double > mNodeLocationsVoronoiDiagram
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > mBalancingFluidSources
unsigned AddNode(Node< SPACE_DIM > *pNewNode)
double GetAverageNodeSpacingOfElement(unsigned index, bool recalculate=true)
virtual c_vector< double, 3 > CalculateMomentsOfElement(unsigned index)
void ConformToGeometry(c_vector< double, SPACE_DIM > &rLocation)
const boost::polygon::voronoi_diagram< double > & rGetNodeLocationsVoronoiDiagram(bool update=true)
void ReMeshElement(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, bool randomOrder)
multi_array< double, 3 > m2dVelocityGrids
std::set< unsigned > GetNeighbouringNodeIndices(unsigned nodeIndex)
unsigned DivideElementAlongGivenAxis(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, c_vector< double, SPACE_DIM > axisOfDivision, bool placeOriginalElementBelow=false)
virtual unsigned GetNumElements() const
friend class boost::serialization::access
void SetNumGridPtsX(unsigned meshPointsX)
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > & rGetBalancingFluidSources()
double GetAverageNodeSpacingOfLamina(unsigned index, bool recalculate=true)
int ScaleUpToVoronoiCoordinate(double location) const
unsigned SolveBoundaryElementMapping(unsigned index) const
unsigned DivideElementAlongShortAxis(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, bool placeOriginalElementBelow=false)
virtual c_vector< double, SPACE_DIM > GetCentroidOfElement(unsigned index)
void SetElementDivisionSpacing(double elementDivisionSpacing)
c_vector< double, SPACE_DIM > GetShortAxisOfElement(unsigned index)
std::set< unsigned > GetNeighbouringElementIndices(unsigned elemIdx)
const multi_array< double, 3 > & rGet2dVelocityGrids() const
std::vector< ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > * > mLaminas
void ConstructFromMeshReader(AbstractMeshReader< ELEMENT_DIM, SPACE_DIM > &rMeshReader)
ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * GetElement(unsigned index) const
double GetVoronoiSurfaceAreaOfElement(unsigned elemIdx)
virtual double GetSurfaceAreaOfElement(unsigned index)
const std::vector< unsigned int > & GetVoronoiCellIdsIndexedByNodeIndex() const
c_vector< double, SPACE_DIM > GetVectorFromAtoB(const c_vector< double, SPACE_DIM > &rLocation1, const c_vector< double, SPACE_DIM > &rLocation2)
void SetNumGridPtsY(unsigned meshPointsY)
unsigned GetNumAllElements() const
Definition Node.hpp:59