Chaste Commit::f2ff7ee04e70ac9d06c57344df8d017dbb12b97b
ImmersedBoundarySimulationModifier.cpp
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#include "ImmersedBoundarySimulationModifier.hpp"
37
38#include <memory>
39
40#include "FluidSource.hpp"
41#include "RandomNumberGenerator.hpp"
42#include "Warnings.hpp"
43
44template<unsigned DIM>
47 mpMesh(nullptr),
48 mpCellPopulation(nullptr),
49 mNodeNeighbourUpdateFrequency(1u),
50 mGridSpacingX(0.0),
51 mGridSpacingY(0.0),
52 mFftNorm(0.0),
53 mAdditiveNormalNoise(false),
54 mNoiseStrength(0.0),
55 mNoiseSkip(1u),
56 mNoiseLengthScale(0.1),
57 mZeroFieldSums(false),
58 mpBoxCollection(nullptr),
59 mReynoldsNumber(1e-4),
60 mI(0.0, 1.0),
61 mpArrays(nullptr),
62 mpFftInterface(nullptr),
63 mpRandomField(nullptr)
64{
65}
66
67template<unsigned DIM>
69 AbstractCellPopulation<DIM,DIM>& rCellPopulation)
70{
71 // We need to update node neighbours occasionally, but not necessarily each timestep
72 if (SimulationTime::Instance()->GetTimeStepsElapsed() % mNodeNeighbourUpdateFrequency == 0)
73 {
74 mpBoxCollection->CalculateNodePairs(mpMesh->rGetNodes(), mNodePairs);
75 }
76
77 // This will solve the fluid problem for all timesteps after the first, which is handled in SetupSolve()
78 this->UpdateFluidVelocityGrids(rCellPopulation);
79}
80
81template<unsigned DIM>
83 AbstractCellPopulation<DIM,DIM>& rCellPopulation,
84 std::string outputDirectory)
85{
86 /*
87 * We can set up some helper variables here which need only be set up once
88 * for the entire simulation.
89 */
90 this->SetupConstantMemberVariables(rCellPopulation);
91
92 // This will solve the fluid problem based on the initial mesh setup
93 this->UpdateFluidVelocityGrids(rCellPopulation);
94}
95
96template<unsigned DIM>
98 out_stream& rParamsFile)
99{
100 // No parameters to output, so just call method on direct parent class
102}
103
104template<unsigned DIM>
106 AbstractCellPopulation<DIM,DIM>& rCellPopulation)
107{
108 this->ClearForcesAndSources();
109 this->RecalculateAverageNodeSpacings();
110 this->AddImmersedBoundaryForceContributions();
111 this->PropagateForcesToFluidGrid();
112
113 // If random noise is required, add it to the force grids
114 if (mAdditiveNormalNoise)
115 {
116 this->AddNormalNoise();
117 }
118
119 // If sources are active, we must propagate them from their nodes to the grid
120 if (mpCellPopulation->DoesPopulationHaveActiveSources())
121 {
122 this->PropagateFluidSourcesToGrid();
123 }
124
125 this->SolveNavierStokesSpectral();
126}
127
128template<unsigned DIM>
130 AbstractCellPopulation<DIM,DIM>& rCellPopulation)
131{
132 if (dynamic_cast<ImmersedBoundaryCellPopulation<DIM> *>(&rCellPopulation) == nullptr)
133 {
134 EXCEPTION("Cell population must be immersed boundary");
135 }
136
137 mpCellPopulation = static_cast<ImmersedBoundaryCellPopulation<DIM> *>(&rCellPopulation);
138 mpMesh = &(mpCellPopulation->rGetMesh());
139
140 // Get the size of the mesh
141 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
142 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
143
144 // Get the grid spacing
145 mGridSpacingX = 1.0 / (double) num_grid_ptsX;
146 mGridSpacingY = 1.0 / (double) num_grid_ptsY;
147
148 // Set up the box collection
149 c_vector<double, 2 * 2> domain_size;
150 domain_size(0) = 0.0;
151 domain_size(1) = 1.0;
152 domain_size(2) = 0.0;
153 domain_size(3) = 1.0;
154
155 mpBoxCollection = std::make_unique<ObsoleteBoxCollection<DIM>>(
156 mpCellPopulation->GetInteractionDistance(),
157 domain_size,
158 true, // LCOV_EXCL_LINE
159 true
160 );
161 mpBoxCollection->SetupLocalBoxesHalfOnly();
162 mpBoxCollection->CalculateNodePairs(mpMesh->rGetNodes(), mNodePairs);
163
164 // Set up dimension-dependent variables
165 switch (DIM)
166 {
167 case 2:
168 {
169 mpArrays = std::make_unique<ImmersedBoundary2dArrays<DIM>>(
170 mpMesh,
172 mReynoldsNumber,
173 mpCellPopulation->DoesPopulationHaveActiveSources()
174 );
175
176 mpFftInterface = std::make_unique<ImmersedBoundaryFftInterface<DIM>>(
177 mpMesh,
178 &(mpArrays->rGetModifiableRightHandSideGrids()[0][0][0]),
179 &(mpArrays->rGetModifiableFourierGrids()[0][0][0]),
180 &(mpMesh->rGetModifiable2dVelocityGrids()[0][0][0]),
181 mpCellPopulation->DoesPopulationHaveActiveSources()
182 );
183
184 mFftNorm = (double) num_grid_ptsX * (double) num_grid_ptsY;
185 break;
186 }
187 default:
188 NEVER_REACHED; // Not yet implemented in 3D
189 }
190
191 // Set up random noise, if required
192 if(mAdditiveNormalNoise)
193 {
194 std::array<double, DIM> lower_corner;
195 lower_corner.fill(0.0);
196
197 std::array<double, DIM> upper_corner;
198 upper_corner.fill(1.0);
199
200 std::array<unsigned, DIM> num_grid_pts;
201 num_grid_pts.fill(num_grid_ptsX / mNoiseSkip);
202
203 std::array<bool, DIM> periodicity;
204 periodicity.fill(true);
205
206 const double total_gridpts = std::accumulate(num_grid_pts.begin(),
207 num_grid_pts.end(),
208 1.0,
209 std::multiplies<double>());
210
211 // Warn at this point if parameters are not sensible
212 if (num_grid_ptsX % mNoiseSkip != 0)
213 {
214 WARNING("mNoiseSkip should perfectly divide num_grid_ptsX or adding forces will likely not work as expected.");
215 }
216 if (total_gridpts > 100.0 * 100.0) // This is about the maximum that can be handled
217 {
218 WARNING("You probably have too many grid points for creating a random field. Increase mNoiseSkip");
219 }
220
221 // Set up the random field generator and save it to cache
222 mpRandomField = std::make_unique<UniformGridRandomFieldGenerator<DIM>>(
223 lower_corner,
224 upper_corner,
225 num_grid_pts,
226 periodicity,
227 mNoiseLengthScale
228 );
229
230 }
231}
232
233template<unsigned DIM>
235{
236 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
237 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
238
239 // Clear applied forces on each node
240 for (auto node_iter = mpMesh->GetNodeIteratorBegin(false);
241 node_iter != mpMesh->GetNodeIteratorEnd();
242 ++node_iter)
243 {
244 node_iter->ClearAppliedForce();
245 }
246
247 // Reset force grids to 0 everywhere
248 multi_array<double, 3>& r_force_grids = mpArrays->rGetModifiableForceGrids();
249
250 for (unsigned dim = 0; dim < 2; dim++)
251 {
252 for (unsigned x = 0; x < num_grid_ptsX; x++)
253 {
254 for (unsigned y = 0; y < num_grid_ptsY; y++)
255 {
256 r_force_grids[dim][x][y] = 0.0;
257 }
258 }
259 }
260
261 // If there are active sources, the relevant grid needs to be reset to zero everywhere
262 if (mpCellPopulation->DoesPopulationHaveActiveSources())
263 {
264 multi_array<double, 3> &r_rhs_grid = mpArrays->rGetModifiableRightHandSideGrids();
265
266 for (unsigned x = 0; x < num_grid_ptsX; x++)
267 {
268 for (unsigned y = 0; y < num_grid_ptsY; y++)
269 {
270 r_rhs_grid[2][x][y] = 0.0;
271 }
272 }
273 }
274}
275
276template<unsigned DIM>
278{
279 for (auto elem_it = mpMesh->GetElementIteratorBegin(false);
280 elem_it != mpMesh->GetElementIteratorEnd();
281 ++elem_it)
282 {
283 mpMesh->GetAverageNodeSpacingOfElement(elem_it->GetIndex(), true);
284 }
285
286 for (auto lam_it = mpMesh->GetLaminaIteratorBegin(false);
287 lam_it != mpMesh->GetLaminaIteratorEnd();
288 ++lam_it)
289 {
290 mpMesh->GetAverageNodeSpacingOfLamina(lam_it->GetIndex(), true);
291 }
292}
293
294template<unsigned DIM>
296{
297 // Add contributions from each immersed boundary force
298 for (auto iter = mForceCollection.begin();
299 iter != mForceCollection.end();
300 ++iter)
301 {
302 (*iter)->AddImmersedBoundaryForceContribution(mNodePairs, *mpCellPopulation);
303 }
304}
305
306template<unsigned DIM>
308{
309 if constexpr (DIM == 2)
310 {
311 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
312 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
313
314 // Helper variables, pre-defined for efficiency
315 double dl;
316 double weight;
317
318 unsigned first_idx_x;
319 unsigned first_idx_y;
320
321 std::vector<unsigned> x_indices(4);
322 std::vector<unsigned> y_indices(4);
323
324 std::vector<double> x_deltas(4);
325 std::vector<double> y_deltas(4);
326
327 c_vector<double, DIM> node_location;
328 c_vector<double, DIM> applied_force;
329
330 // Get a reference to the force grids which we spread the applied forces to
331 multi_array<double, 3>& r_force_grids = mpArrays->rGetModifiableForceGrids();
332
333 // Here, we loop over elements and then nodes as the length scale dl varies per element
334 for (auto elem_iter = mpMesh->GetElementIteratorBegin(false);
335 elem_iter != mpMesh->GetElementIteratorEnd();
336 ++elem_iter)
337 {
338 dl = mpMesh->GetAverageNodeSpacingOfElement(elem_iter->GetIndex(), false);
339
340 for (unsigned node_idx = 0; node_idx < elem_iter->GetNumNodes(); node_idx++)
341 {
342 Node<DIM> *p_node = elem_iter->GetNode(node_idx);
343
344 // Get location and applied force contribution of current node
345 node_location = p_node->rGetLocation();
346 applied_force = p_node->rGetAppliedForce();
347
348 // Get first grid index in each dimension, taking account of possible wrap-around
349 first_idx_x = unsigned(floor(node_location[0] / mGridSpacingX)) + num_grid_ptsX - 1;
350 first_idx_y = unsigned(floor(node_location[1] / mGridSpacingY)) + num_grid_ptsY - 1;
351
352 // Calculate all four indices and deltas in each dimension
353 for (unsigned i = 0; i < 4; i++)
354 {
355 x_indices[i] = (first_idx_x + i) % num_grid_ptsX;
356 y_indices[i] = (first_idx_y + i) % num_grid_ptsY;
357
358 x_deltas[i] = Delta1D(fabs(x_indices[i] * mGridSpacingX - node_location[0]), mGridSpacingX);
359 y_deltas[i] = Delta1D(fabs(y_indices[i] * mGridSpacingY - node_location[1]), mGridSpacingY);
360 }
361
362 // Loop over the 4x4 grid used to spread the force on the nodes to the fluid grid
363 for (unsigned x_idx = 0; x_idx < 4; ++x_idx)
364 {
365 for (unsigned y_idx = 0; y_idx < 4; ++y_idx)
366 {
367 // The applied force is weighted by the delta function
368 weight = x_deltas[x_idx] * y_deltas[y_idx] * dl / (mGridSpacingX * mGridSpacingY);
369
370 r_force_grids[0][x_indices[x_idx]][y_indices[y_idx]] += applied_force[0] * weight;
371 r_force_grids[1][x_indices[x_idx]][y_indices[y_idx]] += applied_force[1] * weight;
372 }
373 }
374 }
375 }
376
377 // Here, we loop over laminas and then nodes as the length scale dl varies per element
378 for (auto lam_iter = mpMesh->GetLaminaIteratorBegin(false);
379 lam_iter != mpMesh->GetLaminaIteratorEnd();
380 ++lam_iter)
381 {
382 dl = mpMesh->GetAverageNodeSpacingOfLamina(lam_iter->GetIndex(), false);
383
384 for (unsigned node_idx = 0; node_idx < lam_iter->GetNumNodes(); ++node_idx)
385 {
386 Node<DIM> *p_node = lam_iter->GetNode(node_idx);
387
388 // Get location and applied force contribution of current node
389 node_location = p_node->rGetLocation();
390 applied_force = p_node->rGetAppliedForce();
391
392 // Get first grid index in each dimension, taking account of possible wrap-around
393 first_idx_x = unsigned(floor(node_location[0] / mGridSpacingX)) + num_grid_ptsX - 1;
394 first_idx_y = unsigned(floor(node_location[1] / mGridSpacingY)) + num_grid_ptsY - 1;
395
396 // Calculate all four indices and deltas in each dimension
397 for (unsigned i = 0; i < 4; i++)
398 {
399 x_indices[i] = (first_idx_x + i) % num_grid_ptsX;
400 y_indices[i] = (first_idx_y + i) % num_grid_ptsY;
401
402 x_deltas[i] = Delta1D(fabs(x_indices[i] * mGridSpacingX - node_location[0]), mGridSpacingX);
403 y_deltas[i] = Delta1D(fabs(y_indices[i] * mGridSpacingY - node_location[1]), mGridSpacingY);
404 }
405
406 // Loop over the 4x4 grid used to spread the force on the nodes to the fluid grid
407 for (unsigned x_idx = 0; x_idx < 4; x_idx++)
408 {
409 for (unsigned y_idx = 0; y_idx < 4; y_idx++)
410 {
411 // The applied force is weighted by the delta function
412 weight = x_deltas[x_idx] * y_deltas[y_idx] * dl / (mGridSpacingX * mGridSpacingY);
413
414 r_force_grids[0][x_indices[x_idx]][y_indices[y_idx]] += applied_force[0] * weight;
415 r_force_grids[1][x_indices[x_idx]][y_indices[y_idx]] += applied_force[1] * weight;
416 }
417 }
418 }
419 }
420
421 // Finally, zero out any systematic small errors on the force grids that may lead to a drift, if required
422 if (mZeroFieldSums)
423 {
424 ZeroFieldSums(r_force_grids);
425 }
426 }
427 else
428 {
430 }
431}
432
433template<unsigned DIM>
435{
436 if constexpr (DIM == 2)
437 {
438 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
439 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
440
441 // Helper variables, pre-defined for efficiency
442 double weight;
443
444 unsigned first_idx_x;
445 unsigned first_idx_y;
446
447 std::vector<unsigned> x_indices(4);
448 std::vector<unsigned> y_indices(4);
449
450 std::vector<double> x_deltas(4);
451 std::vector<double> y_deltas(4);
452
453 // Currently the fluid source grid is the final part of the right hand side grid, as having all three grids
454 // contiguous helps improve the Fourier transform performance.
455 multi_array<double, 3>& r_rhs_grids = mpArrays->rGetModifiableRightHandSideGrids();
456
457 std::vector<std::shared_ptr<FluidSource<DIM>>>& r_element_sources = mpMesh->rGetElementFluidSources();
458 std::vector<std::shared_ptr<FluidSource<DIM>>>& r_balance_sources = mpMesh->rGetBalancingFluidSources();
459
460 // Construct a vector of all sources combined
461 std::vector<std::shared_ptr<FluidSource<DIM>>> combined_sources;
462 combined_sources.insert(combined_sources.end(), r_element_sources.begin(), r_element_sources.end());
463 combined_sources.insert(combined_sources.end(), r_balance_sources.begin(), r_balance_sources.end());
464
465 // Find the combined element source strength
466 double cumulative_strength = 0.0;
467 for (unsigned source_idx = 0; source_idx < r_element_sources.size(); source_idx++)
468 {
469 cumulative_strength += r_element_sources[source_idx]->GetStrength();
470 }
471
472 // Calculate the required balancing strength, and apply it to all balancing sources
473 double balance_strength = -1.0 * cumulative_strength / (double)r_balance_sources.size();
474 for (unsigned source_idx = 0; source_idx < r_balance_sources.size(); source_idx++)
475 {
476 r_balance_sources[source_idx]->SetStrength(balance_strength);
477 }
478
479 // Iterate over all sources and propagate their effects to the source grid
480 for (unsigned source_idx = 0; source_idx < combined_sources.size(); source_idx++)
481 {
482 std::shared_ptr<FluidSource<DIM>> this_source = combined_sources[source_idx];
483
484 // Get location and strength of this source
485 c_vector<double, DIM> source_location = this_source->rGetLocation();
486 double source_strength = this_source->GetStrength();
487
488 // Get first grid index in each dimension, taking account of possible wrap-around
489 first_idx_x = unsigned(floor(source_location[0] / mGridSpacingX)) + num_grid_ptsX - 1;
490 first_idx_y = unsigned(floor(source_location[1] / mGridSpacingY)) + num_grid_ptsY - 1;
491
492 // Calculate all four indices and deltas in each dimension
493 for (unsigned i = 0; i < 4; i ++)
494 {
495 x_indices[i] = (first_idx_x + i) % num_grid_ptsX;
496 y_indices[i] = (first_idx_y + i) % num_grid_ptsY;
497
498 x_deltas[i] = Delta1D(fabs(x_indices[i] * mGridSpacingX - source_location[0]), mGridSpacingX);
499 y_deltas[i] = Delta1D(fabs(y_indices[i] * mGridSpacingY - source_location[1]), mGridSpacingY);
500 }
501
502 // Loop over the 4x4 grid needed to spread the source strength to the source grid
503 for (unsigned x_idx = 0; x_idx < 4; ++x_idx)
504 {
505 for (unsigned y_idx = 0; y_idx < 4; ++y_idx)
506 {
507 // The strength is weighted by the delta function
508 weight = x_deltas[x_idx] * y_deltas[y_idx] / (mGridSpacingX * mGridSpacingY);
509
510 r_rhs_grids[2][x_indices[x_idx]][y_indices[y_idx]] += source_strength * weight;
511 }
512 }
513 }
514 }
515 else
516 {
518 }
519}
520
521template<unsigned DIM>
523{
524 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
525 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
526
527 double dt = SimulationTime::Instance()->GetTimeStep();
528 unsigned reduced_size = 1 + (num_grid_ptsY/2);
529
530 // Get references to all the necessary grids
531 multi_array<double, 3>& r_vel_grids = mpMesh->rGetModifiable2dVelocityGrids();
532 multi_array<double, 3>& r_force_grids = mpArrays->rGetModifiableForceGrids();
533 multi_array<double, 3>& r_rhs_grids = mpArrays->rGetModifiableRightHandSideGrids();
534 multi_array<double, 3>& r_source_gradient_grids = mpArrays->rGetModifiableSourceGradientGrids();
535
536 const multi_array<double, 2>& r_op_1 = mpArrays->rGetOperator1();
537 const multi_array<double, 2>& r_op_2 = mpArrays->rGetOperator2();
538 const std::vector<double>& r_sin_2x = mpArrays->rGetSin2x();
539 const std::vector<double>& r_sin_2y = mpArrays->rGetSin2y();
540
541 multi_array<std::complex<double>, 3>& r_fourier_grids = mpArrays->rGetModifiableFourierGrids();
542 multi_array<std::complex<double>, 2>& r_pressure_grid = mpArrays->rGetModifiablePressureGrid();
543
544 // Perform upwind differencing and create RHS of linear system
545 Upwind2d(r_vel_grids, r_rhs_grids);
546
547 // If the population has active sources, the Right Hand Side grids are calculated differently.
548 if (mpCellPopulation->DoesPopulationHaveActiveSources())
549 {
550 double factor = 1.0 / (3.0 * mReynoldsNumber);
551
552 CalculateSourceGradients(r_rhs_grids, r_source_gradient_grids);
553
554 for (unsigned dim = 0; dim < 2; ++dim)
555 {
556 for (unsigned x = 0; x < num_grid_ptsX; ++x)
557 {
558 for (unsigned y = 0; y < num_grid_ptsY; ++y)
559 {
560 r_rhs_grids[dim][x][y] = r_vel_grids[dim][x][y] + dt * (r_force_grids[dim][x][y] + factor * r_source_gradient_grids[dim][x][y] - r_rhs_grids[dim][x][y]);
561 }
562 }
563 }
564 }
565 else
566 {
567 for (unsigned dim = 0; dim < 2; ++dim)
568 {
569 for (unsigned x = 0; x < num_grid_ptsX; ++x)
570 {
571 for (unsigned y = 0; y < num_grid_ptsY; ++y)
572 {
573 r_rhs_grids[dim][x][y] = (r_vel_grids[dim][x][y] + dt * (r_force_grids[dim][x][y] - r_rhs_grids[dim][x][y]));
574 }
575 }
576 }
577 }
578
579 // Perform fft on r_rhs_grids; results go to r_fourier_grids
580 mpFftInterface->FftExecuteForward();
581
582 /*
583 * The result of a DFT of n real datapoints is n/2 + 1 complex values, due
584 * to redundancy: element n-1 is conj(2), etc. A similar pattern of
585 * redundancy occurs in a 2D transform. Calculations in the Fourier domain
586 * preserve this redundancy, and so all calculations need only be done on
587 * reduced-size arrays, saving memory and computation.
588 */
589
590 // If the population has active fluid sources, the computation is slightly more complicated
591 if (mpCellPopulation->DoesPopulationHaveActiveSources())
592 {
593 for (unsigned x = 0; x < num_grid_ptsX; ++x)
594 {
595 for (unsigned y = 0; y < reduced_size; ++y)
596 {
597 r_pressure_grid[x][y] = (r_op_2[x][y] * r_fourier_grids[2][x][y] - mI * (r_sin_2x[x] * r_fourier_grids[0][x][y] / mGridSpacingX +
598 r_sin_2y[y] * r_fourier_grids[1][x][y] / mGridSpacingY)) / r_op_1[x][y];
599 }
600 }
601 }
602 else // no active fluid sources
603 {
604 for (unsigned x = 0; x < num_grid_ptsX; ++x)
605 {
606 for (unsigned y = 0; y < reduced_size; ++y)
607 {
608 r_pressure_grid[x][y] = -mI * (r_sin_2x[x] * r_fourier_grids[0][x][y] / mGridSpacingX +
609 r_sin_2y[y] * r_fourier_grids[1][x][y] / mGridSpacingY) / r_op_1[x][y];
610 }
611 }
612 }
613
614 // Set some values to zero
615 r_pressure_grid[0][0] = 0.0;
616 r_pressure_grid[num_grid_ptsX/2][0] = 0.0;
617 r_pressure_grid[num_grid_ptsX/2][num_grid_ptsY/2] = 0.0;
618 r_pressure_grid[0][num_grid_ptsY/2] = 0.0;
619
620 /*
621 * Do final stage of computation before inverse FFT. We do the necessary DFT
622 * scaling at this stage so the output from the inverse DFT is correct.
623 */
624 for (unsigned x = 0; x < num_grid_ptsX; ++x)
625 {
626 for (unsigned y = 0; y < reduced_size; ++y)
627 {
628 r_fourier_grids[0][x][y] = (r_fourier_grids[0][x][y] - (mI * dt / (mReynoldsNumber * mGridSpacingX)) * r_sin_2x[x] * r_pressure_grid[x][y]) / (r_op_2[x][y]);
629 r_fourier_grids[1][x][y] = (r_fourier_grids[1][x][y] - (mI * dt / (mReynoldsNumber * mGridSpacingY)) * r_sin_2y[y] * r_pressure_grid[x][y]) / (r_op_2[x][y]);
630 }
631 }
632
633 // Perform inverse fft on r_fourier_grids; results are in r_vel_grids. Then, normalise the DFT.
634 mpFftInterface->FftExecuteInverse();
635 for (unsigned dim = 0; dim < 2; ++dim)
636 {
637 for (unsigned x = 0; x < num_grid_ptsX; ++x)
638 {
639 for (unsigned y = 0; y < num_grid_ptsY; ++y)
640 {
641 r_vel_grids[dim][x][y] /= mFftNorm;
642 }
643 }
644 }
645
646 /*
647 * Finally, zero out any systematic small errors on the velocity grids that
648 * may lead to a drift, if required.
649 */
650 if (mZeroFieldSums)
651 {
652 ZeroFieldSums(r_vel_grids);
653 }
654}
655
656template<unsigned DIM>
658 multi_array<double, 3>& rField)
659{
660 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
661 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
662
663 for (unsigned dim = 0; dim < 2; ++dim)
664 {
665 double total_field_val = 0.0;
666 long count = 0;
667
668 for (unsigned x = 0; x < num_grid_ptsX; ++x)
669 {
670 for (unsigned y = 0; y < num_grid_ptsY; ++y)
671 {
672 if (rField[dim][x][y] != 0.0)
673 {
674 total_field_val += rField[dim][x][y];
675 count++;
676 }
677 }
678 }
679
680 const double average_field_val = (total_field_val / count);
681
682 for (unsigned x = 0; x < num_grid_ptsX; ++x)
683 {
684 for (unsigned y = 0; y < num_grid_ptsY; ++y)
685 {
686 if (rField[dim][x][y] != 0.0)
687 {
688 rField[dim][x][y] -= average_field_val;
689 }
690 }
691 }
692 }
693}
694
695template<unsigned DIM>
697 double dist,
698 double spacing)
699{
700 return (0.25 * (1.0 + cos(M_PI * dist / (2 * spacing))));
701}
702
703template<unsigned DIM>
705 const multi_array<double, 3>& rInput,
706 multi_array<double, 3>& rOutput)
707{
708 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
709 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsX();
710
711 unsigned prev_x = num_grid_ptsX - 1;
712 unsigned prev_y = num_grid_ptsY - 1;
713
714 unsigned next_x = 1;
715 unsigned next_y = 1;
716
717 for (unsigned x = 0; x < num_grid_ptsX; x++)
718 {
719 for (unsigned y = 0; y < num_grid_ptsY; y++)
720 {
721 // Set values for rOutput from conditional on x grid
722 if (rInput[0][x][y] > 0)
723 {
724 rOutput[0][x][y] = rInput[0][x][y] * (rInput[0][x][y] - rInput[0][prev_x][y]) / mGridSpacingX;
725 rOutput[1][x][y] = rInput[0][x][y] * (rInput[1][x][y] - rInput[1][prev_x][y]) / mGridSpacingX;
726 }
727 else
728 {
729 rOutput[0][x][y] = rInput[0][x][y] * (rInput[0][next_x][y] - rInput[0][x][y]) / mGridSpacingX;
730 rOutput[1][x][y] = rInput[0][x][y] * (rInput[1][next_x][y] - rInput[1][x][y]) / mGridSpacingX;
731 }
732
733 // Then add values from conditional on y grid
734 if (rInput[1][x][y] > 0)
735 {
736 rOutput[0][x][y] += rInput[1][x][y] * (rInput[0][x][y] - rInput[0][x][prev_y]) / mGridSpacingY;
737 rOutput[1][x][y] += rInput[1][x][y] * (rInput[1][x][y] - rInput[1][x][prev_y]) / mGridSpacingY;
738 }
739 else
740 {
741 rOutput[0][x][y] += rInput[1][x][y] * (rInput[0][x][next_y] - rInput[0][x][y]) / mGridSpacingY;
742 rOutput[1][x][y] += rInput[1][x][y] * (rInput[1][x][next_y] - rInput[1][x][y]) / mGridSpacingY;
743 }
744
745 prev_y = (prev_y + 1) % num_grid_ptsY;
746 next_y = (next_y + 1) % num_grid_ptsY;
747 }
748
749 prev_x = (prev_x + 1) % num_grid_ptsX;
750 next_x = (next_x + 1) % num_grid_ptsX;
751 }
752}
753
754template<unsigned DIM>
756 const multi_array<double, 3>& rRhs,
757 multi_array<double, 3>& rGradients)
758{
759 unsigned num_grid_ptsX = mpMesh->GetNumGridPtsX();
760 unsigned num_grid_ptsY = mpMesh->GetNumGridPtsY();
761
762 // Assumes 1.0 x 1.0 square domain
763 double factor = 1.0 / (2.0 * mGridSpacingX);
764
765 // The fluid sources are stored in the third slice of the rRhs grids
766 for (unsigned x = 0; x < num_grid_ptsX; ++x)
767 {
768 unsigned next_x = (x + 1) % num_grid_ptsX;
769 unsigned prev_x = (x + num_grid_ptsX - 1) % num_grid_ptsX;
770
771 for (unsigned y = 0; y < num_grid_ptsY; ++y)
772 {
773 unsigned next_y = (y + 1) % num_grid_ptsY;
774 unsigned prev_y = (y + num_grid_ptsY - 1) % num_grid_ptsY;
775
776 rGradients[0][x][y] = factor * (rRhs[2][next_x][y] - rRhs[2][prev_x][y]);
777 rGradients[1][x][y] = factor * (rRhs[2][x][next_y] - rRhs[2][x][prev_y]);
778 }
779 }
780}
781
782template<unsigned DIM>
784 unsigned newFrequency)
785{
786 assert(newFrequency > 0);
787 mNodeNeighbourUpdateFrequency = newFrequency;
788}
789
790template<unsigned DIM>
792{
793 return mNodeNeighbourUpdateFrequency;
794}
795
796template<unsigned DIM>
798 boost::shared_ptr<AbstractImmersedBoundaryForce<DIM> > pForce)
799{
800 mForceCollection.push_back(pForce);
801}
802
803template<unsigned DIM>
805{
806 auto& r_force_grids = mpArrays->rGetModifiableForceGrids();
807
808 // This scaling is to ensure "correct" scaling with timestep
809 const double force_scale_factor = std::sqrt(2.0 * mNoiseStrength / SimulationTime::Instance()->GetTimeStep());
810
811 // Add the noise
812 for (unsigned dim = 0; dim < r_force_grids.shape()[0]; dim++)
813 {
814 // Get an instance of the random field for the current dimension
815 const std::vector<double> field = mpRandomField->SampleRandomField();
816
817 // Calculate the sum of the random field, which we must adjust to have a net-zero impact
818 const double adjustment = std::accumulate(field.begin(), field.end(), 0.0) / field.size();
819
820 for (unsigned x = 0, idx = 0; x < r_force_grids.shape()[1]; x += mNoiseSkip)
821 {
822 for (unsigned y = 0; y < r_force_grids.shape()[2]; y += mNoiseSkip, idx++)
823 {
824 // Value from the random grid at this location
825 const double val = force_scale_factor * (field[idx] - adjustment);
826
827 // Fill in the mSkip x mSkip square of points
828 for (unsigned i = 0; i < mNoiseSkip; ++i)
829 {
830 for (unsigned j = 0; j < mNoiseSkip; ++j)
831 {
832 r_force_grids[dim][x+i][y+j] += val;
833 }
834 }
835 }
836 }
837 }
838}
839
840template<unsigned DIM>
842 double reynoldsNumber)
843{
844 assert(reynoldsNumber > 0.0);
845 mReynoldsNumber = reynoldsNumber;
846}
847
848template<unsigned DIM>
850{
851 return mReynoldsNumber;
852}
853
854template <unsigned DIM>
856{
857 return mAdditiveNormalNoise;
858}
859
860template <unsigned DIM>
862 bool additiveNormalNoise)
863{
864 mAdditiveNormalNoise = additiveNormalNoise;
865}
866
867template <unsigned DIM>
869{
870 return mNoiseStrength;
871}
872
873template <unsigned DIM>
875 double noiseStrength)
876{
877 mNoiseStrength = noiseStrength;
878}
879
880template <unsigned DIM>
882{
883 return mNoiseSkip;
884}
885
886template <unsigned DIM>
888{
889 mNoiseSkip = noiseSkip;
890}
891
892template <unsigned DIM>
894{
895 return mNoiseLengthScale;
896}
897
898template <unsigned DIM>
900 double noiseLengthScale)
901{
902 mNoiseLengthScale = noiseLengthScale;
903}
904
905template<unsigned int DIM>
907{
908 return mZeroFieldSums;
909}
910
911template<unsigned int DIM>
913 bool zeroFieldSums)
914{
915 mZeroFieldSums = zeroFieldSums;
916}
917
918// Explicit instantiation
922
923// Serialization for Boost >= 1.36
926
#define EXCEPTION(message)
#define NEVER_REACHED
#define EXPORT_TEMPLATE_CLASS_SAME_DIMS(CLASS)
virtual void OutputSimulationModifierParameters(out_stream &rParamsFile)=0
void CalculateSourceGradients(const multi_array< double, 3 > &rRhs, multi_array< double, 3 > &rGradients)
void Upwind2d(const multi_array< double, 3 > &rInput, multi_array< double, 3 > &rOutput)
void SetupConstantMemberVariables(AbstractCellPopulation< DIM, DIM > &rCellPopulation)
void AddImmersedBoundaryForce(boost::shared_ptr< AbstractImmersedBoundaryForce< DIM > > pForce)
virtual void UpdateAtEndOfTimeStep(AbstractCellPopulation< DIM, DIM > &rCellPopulation)
void OutputSimulationModifierParameters(out_stream &rParamsFile)
virtual void SetupSolve(AbstractCellPopulation< DIM, DIM > &rCellPopulation, std::string outputDirectory)
void UpdateFluidVelocityGrids(AbstractCellPopulation< DIM, DIM > &rCellPopulation)
void ZeroFieldSums(multi_array< double, 3 > &rField)
Definition Node.hpp:59
c_vector< double, SPACE_DIM > & rGetAppliedForce()
Definition Node.cpp:208
const c_vector< double, SPACE_DIM > & rGetLocation() const
Definition Node.cpp:139
double GetTimeStep() const
static SimulationTime * Instance()