Skip to content

Commit 1b5704e

Browse files
authored
Merge pull request #464 from llnl/feature/PairAcclerationRefactor
Refactoring use of pair-wise fields in hydrodynamics packages to avoid pointers
2 parents 43c6d64 + 3bf402b commit 1b5704e

22 files changed

Lines changed: 108 additions & 112 deletions

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Notable changes include:
66

77
* New features / API changes:
88
* Added view class for PairwiseField (PairwiseFieldView)
9+
* Refactored use of pair-wise fields in hydro packages to avoid using pointers and allow empty PairwiseFields
910

1011
* Bug fixes
1112

src/CRKSPH/CRKSPH.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ CRKSPH(DataBase<Dimension>& dataBase,
8080
densityUpdate,
8181
epsTensile,
8282
nTensile),
83-
mPairAccelerationsPtr() {
83+
mPairAccelerationsPtr(std::make_unique<PairAccelerationsType>()) {
8484
}
8585

8686
//------------------------------------------------------------------------------
@@ -134,8 +134,8 @@ registerDerivatives(DataBase<Dimension>& dataBase,
134134
if (compatibleEnergy) {
135135
const auto& connectivityMap = dataBase.connectivityMap();
136136
mPairAccelerationsPtr = std::make_unique<PairAccelerationsType>(connectivityMap);
137-
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
138137
}
138+
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
139139
TIME_END("CRKregisterDerivatives");
140140
}
141141

@@ -240,7 +240,7 @@ evaluateDerivativesImpl(const typename Dimension::Scalar /*time*/,
240240
auto maxViscousPressure = derivs.fields(HydroFieldNames::maxViscousPressure, 0.0);
241241
auto effViscousPressure = derivs.fields(HydroFieldNames::effectiveViscousPressure, 0.0);
242242
auto XSPHDeltaV = derivs.fields(HydroFieldNames::XSPHDeltaV, Vector::zero());
243-
auto* pairAccelerationsPtr = derivs.template getPtr<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
243+
auto& pairAccelerations = derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
244244
CHECK(DxDt.size() == numNodeLists);
245245
CHECK(DrhoDt.size() == numNodeLists);
246246
CHECK(DvDt.size() == numNodeLists);
@@ -250,7 +250,7 @@ evaluateDerivativesImpl(const typename Dimension::Scalar /*time*/,
250250
CHECK(maxViscousPressure.size() == numNodeLists);
251251
CHECK(effViscousPressure.size() == numNodeLists);
252252
CHECK(XSPHDeltaV.size() == numNodeLists);
253-
CHECK((compatibleEnergy and pairAccelerationsPtr->size() == npairs) or not compatibleEnergy);
253+
CHECK((compatibleEnergy and pairAccelerations.size() == npairs) or not compatibleEnergy);
254254

255255
// Walk all the interacting pairs.
256256
#pragma omp parallel
@@ -378,7 +378,7 @@ evaluateDerivativesImpl(const typename Dimension::Scalar /*time*/,
378378
mj*weighti*((Pj - Pi)/rhoj*gradWi - rhoj*QPiji*gradWi)); // RK
379379
DvDti -= forceij/mi;
380380
DvDtj += forceji/mj;
381-
if (compatibleEnergy) (*pairAccelerationsPtr)[kk] = -forceij/mi; // Acceleration for i (j anti-symmetric)
381+
if (compatibleEnergy) pairAccelerations[kk] = -forceij/mi; // Acceleration for i (j anti-symmetric)
382382

383383
// Energy
384384
DepsDti += (true ? // surfacePoint(nodeListi, i) <= 1 ?

src/CRKSPH/CRKSPHRZ.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ CRKSPHRZ(DataBase<Dimension>& dataBase,
7878
densityUpdate,
7979
epsTensile,
8080
nTensile),
81-
mPairAccelerationsPtr() {
81+
mPairAccelerationsPtr(std::make_unique<PairAccelerationsType>()) {
8282
}
8383

8484
//------------------------------------------------------------------------------
@@ -166,8 +166,8 @@ registerDerivatives(DataBase<Dim<2>>& dataBase,
166166
if (compatibleEnergy) {
167167
const auto& connectivityMap = dataBase.connectivityMap();
168168
mPairAccelerationsPtr = std::make_unique<PairAccelerationsType>(connectivityMap);
169-
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
170169
}
170+
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
171171
}
172172

173173
//------------------------------------------------------------------------------
@@ -299,7 +299,7 @@ evaluateDerivativesImpl(const Dim<2>::Scalar /*time*/,
299299
auto maxViscousPressure = derivs.fields(HydroFieldNames::maxViscousPressure, 0.0);
300300
auto effViscousPressure = derivs.fields(HydroFieldNames::effectiveViscousPressure, 0.0);
301301
auto XSPHDeltaV = derivs.fields(HydroFieldNames::XSPHDeltaV, Vector::zero());
302-
auto* pairAccelerationsPtr = derivs.template getPtr<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
302+
auto& pairAccelerations = derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
303303
CHECK(DxDt.size() == numNodeLists);
304304
CHECK(DrhoDt.size() == numNodeLists);
305305
CHECK(DvDt.size() == numNodeLists);
@@ -309,7 +309,7 @@ evaluateDerivativesImpl(const Dim<2>::Scalar /*time*/,
309309
CHECK(maxViscousPressure.size() == numNodeLists);
310310
CHECK(effViscousPressure.size() == numNodeLists);
311311
CHECK(XSPHDeltaV.size() == numNodeLists);
312-
CHECK((compatibleEnergy and pairAccelerationsPtr->size() == npairs) or not compatibleEnergy);
312+
CHECK((compatibleEnergy and pairAccelerations.size() == npairs) or not compatibleEnergy);
313313

314314
// Walk all the interacting pairs.
315315
#pragma omp parallel
@@ -434,8 +434,8 @@ evaluateDerivativesImpl(const Dim<2>::Scalar /*time*/,
434434
DvDti -= forceij/mRZi; //CRK Acceleration
435435
DvDtj += forceij/mRZj; //CRK Acceleration
436436
if (compatibleEnergy) {
437-
(*pairAccelerationsPtr)[kk][0] = -forceij/mRZi;
438-
(*pairAccelerationsPtr)[kk][1] = forceij/mRZj;
437+
pairAccelerations[kk][0] = -forceij/mRZi;
438+
pairAccelerations[kk][1] = forceij/mRZj;
439439
}
440440

441441
DepsDti += 0.5*weighti*weightj*(Pj*vij.dot(deltagrad) + workQi)/mRZi; // CRK Q

src/CRKSPH/SolidCRKSPH.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ evaluateDerivativesImpl(const typename Dimension::Scalar /*time*/,
341341
auto effViscousPressure = derivs.fields(HydroFieldNames::effectiveViscousPressure, 0.0);
342342
auto XSPHDeltaV = derivs.fields(HydroFieldNames::XSPHDeltaV, Vector::zero());
343343
auto DSDt = derivs.fields(IncrementState<Dimension, SymTensor>::prefix() + SolidFieldNames::deviatoricStress, SymTensor::zero());
344-
auto* pairAccelerationsPtr = derivs.template getPtr<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
344+
auto& pairAccelerations = derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
345345
CHECK(DxDt.size() == numNodeLists);
346346
CHECK(DrhoDt.size() == numNodeLists);
347347
CHECK(DvDt.size() == numNodeLists);
@@ -352,7 +352,7 @@ evaluateDerivativesImpl(const typename Dimension::Scalar /*time*/,
352352
CHECK(effViscousPressure.size() == numNodeLists);
353353
CHECK(XSPHDeltaV.size() == numNodeLists);
354354
CHECK(DSDt.size() == numNodeLists);
355-
CHECK((compatibleEnergy and pairAccelerationsPtr->size() == npairs) or not compatibleEnergy);
355+
CHECK((compatibleEnergy and pairAccelerations.size() == npairs) or not compatibleEnergy);
356356

357357
// Walk all the interacting pairs.
358358
#pragma omp parallel
@@ -503,7 +503,7 @@ evaluateDerivativesImpl(const typename Dimension::Scalar /*time*/,
503503
DvDti -= forceij/mi;
504504
DvDtj += forceji/mj;
505505
}
506-
if (compatibleEnergy) (*pairAccelerationsPtr)[kk] = -forceij/mi; // Acceleration for i (j anti-symmetric)
506+
if (compatibleEnergy) pairAccelerations[kk] = -forceij/mi; // Acceleration for i (j anti-symmetric)
507507

508508
// Energy
509509
DepsDti += (true ? // surfacePoint(nodeListi, i) <= 1 ?

src/CRKSPH/SolidCRKSPHRZ.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ SolidCRKSPHRZ(DataBase<Dimension>& dataBase,
119119
epsTensile,
120120
nTensile,
121121
damageRelieveRubble),
122-
mPairAccelerationsPtr(),
122+
mPairAccelerationsPtr(std::make_unique<PairAccelerationsType>()),
123123
mSelfAccelerations(FieldStorageType::CopyFields) {
124124
}
125125

@@ -215,9 +215,9 @@ registerDerivatives(DataBase<Dimension>& dataBase,
215215
const auto& connectivityMap = dataBase.connectivityMap();
216216
mPairAccelerationsPtr = std::make_unique<PairAccelerationsType>(connectivityMap);
217217
dataBase.resizeFluidFieldList(mSelfAccelerations, Vector::zero(), HydroFieldNames::selfAccelerations, false);
218-
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
219218
derivs.enroll(HydroFieldNames::selfAccelerations, mSelfAccelerations);
220219
}
220+
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
221221
}
222222

223223
//------------------------------------------------------------------------------
@@ -368,7 +368,7 @@ evaluateDerivativesImpl(const Dimension::Scalar /*time*/,
368368
auto localDvDx = derivs.fields(HydroFieldNames::internalVelocityGradient, Tensor::zero());
369369
auto maxViscousPressure = derivs.fields(HydroFieldNames::maxViscousPressure, 0.0);
370370
auto effViscousPressure = derivs.fields(HydroFieldNames::effectiveViscousPressure, 0.0);
371-
auto* pairAccelerationsPtr = derivs.template getPtr<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
371+
auto& pairAccelerations = derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
372372
auto selfAccelerations = derivs.fields(HydroFieldNames::selfAccelerations, Vector::zero(), true);
373373
auto XSPHDeltaV = derivs.fields(HydroFieldNames::XSPHDeltaV, Vector::zero());
374374
auto DSDt = derivs.fields(IncrementState<Dimension, SymTensor>::prefix() + SolidFieldNames::deviatoricStress, SymTensor::zero());
@@ -382,9 +382,9 @@ evaluateDerivativesImpl(const Dimension::Scalar /*time*/,
382382
CHECK(effViscousPressure.size() == numNodeLists);
383383
CHECK(XSPHDeltaV.size() == numNodeLists);
384384
CHECK(DSDt.size() == numNodeLists);
385-
CHECK((compatibleEnergy and pairAccelerationsPtr->size() == npairs) or not compatibleEnergy);
386-
CHECK((compatibleEnergy and selfAccelerations.size() == 0u) or
387-
(not compatibleEnergy and selfAccelerations.size() == numNodeLists));
385+
CHECK((compatibleEnergy and pairAccelerations.size() == npairs) or not compatibleEnergy);
386+
CHECK((compatibleEnergy and selfAccelerations.size() == numNodeLists) or
387+
(selfAccelerations.size() == 0u));
388388

389389
// Build the functor we use to compute the effective coupling between nodes.
390390
const NodeCoupling coupling;
@@ -539,8 +539,8 @@ evaluateDerivativesImpl(const Dimension::Scalar /*time*/,
539539
DvDtj += forceji/mRZj;
540540
}
541541
if (compatibleEnergy) {
542-
(*pairAccelerationsPtr)[kk][0] = -forceij/mRZi;
543-
(*pairAccelerationsPtr)[kk][1] = forceji/mRZj;
542+
pairAccelerations[kk][0] = -forceij/mRZi;
543+
pairAccelerations[kk][1] = forceji/mRZj;
544544
}
545545

546546
// Energy

src/FSISPH/SolidFSISPH.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ SolidFSISPH(DataBase<Dimension>& dataBase,
157157
mnTensile(nTensile),
158158
mxmin(xmin),
159159
mxmax(xmax),
160-
mPairAccelerationsPtr(),
161-
mPairDepsDtPtr(),
160+
mPairAccelerationsPtr(std::make_unique<PairAccelerationsType>()),
161+
mPairDepsDtPtr(std::make_unique<PairWorkType>()),
162162
mTimeStepMask(FieldStorageType::CopyFields),
163163
mPressure(FieldStorageType::CopyFields),
164164
mDamagedPressure(FieldStorageType::CopyFields),
@@ -410,9 +410,9 @@ registerDerivatives(DataBase<Dimension>& dataBase,
410410
const auto& connectivityMap = dataBase.connectivityMap();
411411
mPairAccelerationsPtr = std::make_unique<PairAccelerationsType>(connectivityMap);
412412
mPairDepsDtPtr = std::make_unique<PairWorkType>(connectivityMap);
413-
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
414-
derivs.enroll(HydroFieldNames::pairWork, *mPairDepsDtPtr);
415413
}
414+
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
415+
derivs.enroll(HydroFieldNames::pairWork, *mPairDepsDtPtr);
416416

417417
derivs.enroll(plasticStrainRate);
418418
derivs.enroll(mXSPHDeltaV);

src/FSISPH/SolidFSISPHEvaluateDerivatives.cc

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,8 @@ secondDerivativesLoop(const typename Dimension::Scalar time,
173173
auto XSPHWeightSum = derivs.fields(HydroFieldNames::XSPHWeightSum, 0.0);
174174
auto XSPHDeltaV = derivs.fields(HydroFieldNames::XSPHDeltaV, Vector::zero());
175175
auto DSDt = derivs.fields(IncrementState<Dimension, SymTensor>::prefix() + SolidFieldNames::deviatoricStress, SymTensor::zero());
176-
auto* pairAccelerationsPtr = (compatibleEnergy ?
177-
&derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations) :
178-
nullptr);
179-
auto* pairDepsDtPtr = (compatibleEnergy ?
180-
&derivs.template get<PairWorkType>(HydroFieldNames::pairWork) :
181-
nullptr);
176+
auto& pairAccelerations = derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
177+
auto& pairDepsDt = derivs.template get<PairWorkType>(HydroFieldNames::pairWork);
182178
CHECK(M.size() == numNodeLists);
183179
CHECK(localM.size() == numNodeLists);
184180
CHECK(DepsDx.size() == numNodeLists);
@@ -204,8 +200,8 @@ secondDerivativesLoop(const typename Dimension::Scalar time,
204200
CHECK(XSPHWeightSum.size() == numNodeLists);
205201
CHECK(XSPHDeltaV.size() == numNodeLists);
206202
CHECK(DSDt.size() == numNodeLists);
207-
CHECK(not compatibleEnergy or pairAccelerationsPtr->size() == numPairs);
208-
CHECK(not compatibleEnergy or pairDepsDtPtr->size() == numPairs);
203+
CHECK(not compatibleEnergy or pairAccelerations.size() == numPairs);
204+
CHECK(not compatibleEnergy or pairDepsDt.size() == numPairs);
209205

210206
//this->computeMCorrection(time,dt,dataBase,state,derivs);
211207

@@ -626,9 +622,9 @@ secondDerivativesLoop(const typename Dimension::Scalar time,
626622
DepsDtj -= mi*deltaDepsDtj;
627623

628624
if(compatibleEnergy){
629-
(*pairAccelerationsPtr)[kk] = - deltaDvDt;
630-
(*pairDepsDtPtr)[kk][0] = - deltaDepsDti;
631-
(*pairDepsDtPtr)[kk][1] = - deltaDepsDtj;
625+
pairAccelerations[kk] = - deltaDvDt;
626+
pairDepsDt[kk][0] = - deltaDepsDti;
627+
pairDepsDt[kk][1] = - deltaDepsDtj;
632628
}
633629

634630
// thermal diffusion
@@ -638,8 +634,8 @@ secondDerivativesLoop(const typename Dimension::Scalar time,
638634
const auto cijEff = max(min(cij + (vi-vj).dot(rhatij), cij),0.0);
639635
const auto diffusion = epsDiffusionCoeff*cijEff*(epsLineari-epsLinearj)*etaij.dot(gradWij)/(rhoij*etaMagij*etaMagij+tiny);
640636
if (compatibleEnergy) {
641-
(*pairDepsDtPtr)[kk][0] += diffusion;
642-
(*pairDepsDtPtr)[kk][1] -= diffusion;
637+
pairDepsDt[kk][0] += diffusion;
638+
pairDepsDt[kk][1] -= diffusion;
643639
}
644640
}
645641

src/GSPH/GenericRiemannHydro.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ GenericRiemannHydro(DataBase<Dimension>& dataBase,
123123
mRiemannDvDx(FieldStorageType::CopyFields),
124124
mNewRiemannDpDx(FieldStorageType::CopyFields),
125125
mNewRiemannDvDx(FieldStorageType::CopyFields),
126-
mPairAccelerationsPtr(),
127-
mPairDepsDtPtr() {
126+
mPairAccelerationsPtr(std::make_unique<PairAccelerationsType>()),
127+
mPairDepsDtPtr(std::make_unique<PairWorkType>()) {
128128

129129
// Create storage for our internal state.
130130
mTimeStepMask = dataBase.newFluidFieldList(int(0), HydroFieldNames::timeStepMask);
@@ -297,9 +297,9 @@ registerDerivatives(DataBase<Dimension>& dataBase,
297297
const auto& connectivityMap = dataBase.connectivityMap();
298298
mPairAccelerationsPtr = std::make_unique<PairAccelerationsType>(connectivityMap);
299299
mPairDepsDtPtr = std::make_unique<PairWorkType>(connectivityMap);
300-
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
301-
derivs.enroll(HydroFieldNames::pairWork, *mPairDepsDtPtr);
302300
}
301+
derivs.enroll(HydroFieldNames::pairAccelerations, *mPairAccelerationsPtr);
302+
derivs.enroll(HydroFieldNames::pairWork, *mPairDepsDtPtr);
303303
}
304304

305305
//------------------------------------------------------------------------------

src/GSPH/MFMEvaluateDerivatives.cc

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,8 @@ evaluateDerivatives(const typename Dimension::Scalar time,
7171
auto DvDt = derivs.fields(HydroFieldNames::hydroAcceleration, Vector::zero());
7272
auto DepsDt = derivs.fields(IncrementState<Dimension, Scalar>::prefix() + HydroFieldNames::specificThermalEnergy, 0.0);
7373
auto DvDx = derivs.fields(HydroFieldNames::velocityGradient, Tensor::zero());
74-
auto* pairAccelerationsPtr = (compatibleEnergy ?
75-
&derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations) :
76-
nullptr);
77-
auto* pairDepsDtPtr = (compatibleEnergy ?
78-
&derivs.template get<PairWorkType>(HydroFieldNames::pairWork) :
79-
nullptr);
74+
auto& pairAccelerations = derivs.template get<PairAccelerationsType>(HydroFieldNames::pairAccelerations);
75+
auto& pairDepsDt = derivs.template get<PairWorkType>(HydroFieldNames::pairWork);
8076
auto XSPHDeltaV = derivs.fields(HydroFieldNames::XSPHDeltaV, Vector::zero());
8177
auto newRiemannDpDx = derivs.fields(ReplaceState<Dimension, Scalar>::prefix() + GSPHFieldNames::RiemannPressureGradient,Vector::zero());
8278
auto newRiemannDvDx = derivs.fields(ReplaceState<Dimension, Scalar>::prefix() + GSPHFieldNames::RiemannVelocityGradient,Tensor::zero());
@@ -92,8 +88,8 @@ evaluateDerivatives(const typename Dimension::Scalar time,
9288
CHECK(XSPHDeltaV.size() == numNodeLists);
9389
CHECK(newRiemannDpDx.size() == numNodeLists);
9490
CHECK(newRiemannDvDx.size() == numNodeLists);
95-
CHECK(not compatibleEnergy or pairAccelerationsPtr->size() == npairs);
96-
CHECK(not compatibleEnergy or pairDepsDtPtr->size() == npairs);
91+
CHECK(not compatibleEnergy or pairAccelerations.size() == npairs);
92+
CHECK(not compatibleEnergy or pairDepsDt.size() == npairs);
9793

9894
this->computeMCorrection(time,dt,dataBase,state,derivs);
9995

@@ -255,9 +251,9 @@ evaluateDerivatives(const typename Dimension::Scalar time,
255251

256252
if(compatibleEnergy){
257253
const auto invmij = 1.0/(mi*mj);
258-
(*pairAccelerationsPtr)[kk] = deltaDvDt*invmij;
259-
(*pairDepsDtPtr)[kk][0] = deltaDepsDti*invmij;
260-
(*pairDepsDtPtr)[kk][1] = deltaDepsDtj*invmij;
254+
pairAccelerations[kk] = deltaDvDt*invmij;
255+
pairDepsDt[kk][0] = deltaDepsDti*invmij;
256+
pairDepsDt[kk][1] = deltaDepsDtj*invmij;
261257
}
262258

263259
// gradients

src/GSPH/MFV.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ MFV(DataBase<Dimension>& dataBase,
115115
mDmomentumDt(FieldStorageType::CopyFields),
116116
mDvolumeDt(FieldStorageType::CopyFields),
117117
//mHStretchTensor(FieldStorageType::CopyFields),
118-
mPairMassFluxPtr() {
118+
mPairMassFluxPtr(std::make_unique<PairMassFluxType>()) {
119119
// mNodalVelocity = dataBase.newFluidFieldList(Vector::zero(), GSPHFieldNames::nodalVelocity);
120120
mDmassDt = dataBase.newFluidFieldList(0.0, IncrementState<Dimension, Scalar>::prefix() + HydroFieldNames::mass);
121121
mDthermalEnergyDt = dataBase.newFluidFieldList(0.0, IncrementState<Dimension, Scalar>::prefix() + GSPHFieldNames::thermalEnergy);

0 commit comments

Comments
 (0)