PRISMS-PF Manual
Loading...
Searching...
No Matches
nucleation_manager.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: © 2026 PRISMS Center at the University of Michigan
2// SPDX-License-Identifier: GNU Lesser General Public Version 2.1
3
4#pragma once
5
6#include <deal.II/base/mpi.h>
7#include <deal.II/fe/fe_values.h>
8
13#include <prismspf/core/types.h>
14
16
18
23
24#include <prismspf/config.h>
25
26#include <algorithm>
27#include <list>
28#include <mpi.h>
29#include <random>
30#include <vector>
31
33
34// Note: could make NucleationManager a namespace as everything is static
35
40template <unsigned int dim, unsigned int degree, typename number>
42{
43public:
48 static bool
50 std::vector<Nucleus<dim>> &nuclei);
51
56 static unsigned int
57 calculate_number_of_events(const double &rate,
58 const double &delta_t,
59 const double &volume,
60 RNGEngine &rng)
61 {
62 std::poisson_distribution<unsigned int> distribution(rate * delta_t * volume);
63 return distribution(rng);
64 }
65
71 static bool
72 gather_exclude_broadcast_nuclei(std::list<Nucleus<dim>> &new_nuclei_list,
73 std::vector<Nucleus<dim>> &global_nuclei,
74 const UserInputParameters<dim> &user_inputs,
75 const SimulationTimer &time_info);
76
81 static void
82 mpi_gather_nuclei(std::vector<Nucleus<dim>> &local_nuclei);
83
88 static void
89 mpi_broadcast_nuclei(std::vector<Nucleus<dim>> &local_nuclei);
90};
91
92template <unsigned int dim, unsigned int degree, typename number>
93inline bool
95 const SolveContext<dim, degree, number> &solve_context,
96 std::vector<Nucleus<dim>> &nuclei)
97{
98 // Set up references.
99 const UserInputParameters<dim> &user_inputs = solve_context.get_user_inputs();
100 const NucleationParameters &nuc_params = user_inputs.nucleation_parameters;
101 const SimulationTimer &time_info = solve_context.get_simulation_timer();
102 const double delta_t = nuc_params.nucleation_period * time_info.get_timestep();
103 auto &rng = user_inputs.misc_parameters.rng;
104
105 // Set up FEValues
106 unsigned int num_quad_points = SystemWide<dim, degree>::quadrature.size();
107 // Made static because initialization was taking a LOT of time
108 static dealii::FEValues<dim> fe_values(SystemWide<dim, degree>::fe_systems[0],
110 dealii::UpdateFlags::update_values |
111 dealii::UpdateFlags::update_JxW_values);
112 std::list<Nucleus<dim>> new_nuclei_list;
113 // Loop over nucleation rate variables and attempt seeding at each cell
114 for (unsigned int index = 0; index < solve_context.get_field_attributes().size();
115 ++index)
116 {
117 const auto &variable = solve_context.get_field_attributes()[index];
118 if (!variable.is_nucleation_rate_variable)
119 {
120 continue;
121 }
122 std::uniform_int_distribution<unsigned int> nucleating_index_dist(
123 0,
124 variable.nucleating_field_indices.size() - 1);
125 // Perform nucleation logic here
126 // This is where you would check conditions and create nuclei
127 for (const auto &cell : solve_context.get_triangulation_manager()
128 .get_triangulation()
129 .active_cell_iterators())
130 {
131 if (!cell->is_locally_owned())
132 {
133 continue;
134 }
135 std::vector<number> values(num_quad_points, 0.0);
136 // Grab the DoFHandler iterator
137 const auto dof_iterator = cell->as_dof_handler_iterator(
138 solve_context.get_dof_manager().get_field_dof_handler(index));
139
140 // Reinit the cell
141 fe_values.reinit(dof_iterator);
142 // Get the values for a scalar field
143 fe_values.get_function_values(
144 (solve_context.get_solution_indexer().get_solution_vector(index)),
145 values);
146 double nuc_rate = 0.0;
147 double cell_volume = 0.0;
148 for (unsigned int q_point = 0; q_point < num_quad_points; ++q_point)
149 {
150 nuc_rate += values[q_point] * fe_values.get_quadrature().weight(q_point);
151 cell_volume += fe_values.JxW(q_point);
152 }
153 unsigned int num_nuclei_in_cell =
154 calculate_number_of_events(nuc_rate, delta_t, cell_volume, rng);
155 for (unsigned int i = 0; i < num_nuclei_in_cell; ++i)
156 {
157 dealii::Point<dim> nucleus_location_unit_cell;
158 for (unsigned int d = 0; d < dim; ++d)
159 {
160 // Note: if we ever do non-rectangular cells, try a randomly weighted
161 // sum over dealii::GeometryInfo< dim >::unit_cell_vertex
162 static std::uniform_real_distribution<double> uniform_unit_interval(
163 0.0,
164 1.0);
165 nucleus_location_unit_cell[d] = uniform_unit_interval(rng);
166 }
167 dealii::Point<dim> nucleus_location =
169 .transform_unit_to_real_cell(cell, nucleus_location_unit_cell);
170 double seed_time = time_info.get_time();
171 unsigned int seed_increment = time_info.get_increment();
172 unsigned int nucleating_index =
173 variable.nucleating_field_indices[nucleating_index_dist(rng)];
174
175 new_nuclei_list.emplace_back(nucleating_index,
176 nucleus_location,
177 seed_time,
178 seed_increment);
179 }
180 }
181 }
182 return gather_exclude_broadcast_nuclei(new_nuclei_list, nuclei, user_inputs, time_info);
183}
184
185template <unsigned int dim, unsigned int degree, typename number>
186inline bool
188 std::list<Nucleus<dim>> &new_nuclei_list,
189 std::vector<Nucleus<dim>> &global_nuclei,
190 const UserInputParameters<dim> &user_inputs,
191 const SimulationTimer &time_info)
192{
193 // dont waste time if no nuclei appeared
194 if (!bool(dealii::Utilities::MPI::sum(new_nuclei_list.size(), MPI_COMM_WORLD)))
195 {
196 return false;
197 }
198
199 // Set up refs
200 const NucleationParameters &nuc_params = user_inputs.nucleation_parameters;
201 RNGEngine &rng = user_inputs.misc_parameters.rng;
202
203 // Gather new nuclei to root process
204 std::vector<Nucleus<dim>> new_nuclei(new_nuclei_list.begin(), new_nuclei_list.end());
205 mpi_gather_nuclei(new_nuclei);
206 bool any_nucleation_occurred = false;
207 if (dealii::Utilities::MPI::this_mpi_process(MPI_COMM_WORLD) == 0)
208 {
209 // Remove nuclei within their exclusion distance and add to nuclei list
211 << "[Increment " << time_info.get_increment() << "] : Nucleation\n"
212 << " " << new_nuclei.size() << " nuclei generated before exclusion.\n"
213 << " Excluding nuclei...\n";
214 unsigned int count = 0;
215
216 // remove bias from cell order
217 std::shuffle(new_nuclei.begin(), new_nuclei.end(), rng);
218
219 while (!new_nuclei.empty())
220 {
221 const Nucleus<dim> &nuc = new_nuclei.back();
222 bool valid = std::none_of(
223 global_nuclei.begin(),
224 global_nuclei.end(),
225 [&](const Nucleus<dim> &existing_nucleus)
226 {
227 const double distance =
228 user_inputs.spatial_discretization.distance(nuc.location,
229 existing_nucleus.location);
230
231 return nuc_params.check_active(existing_nucleus, time_info) &&
232 (distance < nuc_params.nucleus_exclusion_distance ||
233 (nuc.field_index == existing_nucleus.field_index &&
234 distance < nuc_params.same_field_nucleus_exclusion_distance));
235 });
236 if (valid)
237 {
238 // Note: Using push_back() in a loop is not good use for
239 // vectors. We also don't want to use reserve() on the upper bound
240 // because that could allocate much more space than needed. I originally
241 // was using a std::list to avoid this issue, but that is unfriendly to
242 // the MPI functions. One solution could be to convert between data
243 // structures as needed, but that also adds overhead. For now, I will
244 // assume that the total number of nuclei is not enough to
245 // cause significant performance issues.
246 global_nuclei.push_back(nuc);
248 << " New nucleus at: " << nuc.location << "\n";
249 ++count;
250 any_nucleation_occurred = true;
251 }
252 new_nuclei.pop_back();
253 }
254 ConditionalOStreams::pout_base() << " " << count
255 << " new nuclei after exclusion.\n"
256 " "
257 << global_nuclei.size() << " total nuclei.\n\n"
258 << std::flush;
259 }
260 MPI_Bcast(&any_nucleation_occurred, 1, MPI_CXX_BOOL, 0, MPI_COMM_WORLD);
261 mpi_broadcast_nuclei(global_nuclei);
262 return any_nucleation_occurred;
263}
264
265template <unsigned int dim, unsigned int degree, typename number>
266inline void
268 std::vector<Nucleus<dim>> &local_nuclei)
269{
270 // Step 1: Share how many nuclei each rank has
271 int rank = dealii::Utilities::MPI::this_mpi_process(MPI_COMM_WORLD);
272 int num_procs = dealii::Utilities::MPI::n_mpi_processes(MPI_COMM_WORLD);
273 int local_count = local_nuclei.size();
274 std::vector<int> nuclei_counts_per_rank;
275 if (rank == 0)
276 {
277 nuclei_counts_per_rank.resize(num_procs);
278 }
279
280 MPI_Gather(&local_count,
281 1,
282 MPI_INT,
283 nuclei_counts_per_rank.data(),
284 1,
285 MPI_INT,
286 0,
287 MPI_COMM_WORLD);
288
289 // Step 2: Compute displacements and allocate receive buffer on root
290 std::vector<int> recv_displacements;
291 std::vector<Nucleus<dim>> gathered_nuclei;
292 if (rank == 0)
293 {
294 recv_displacements.resize(num_procs);
295 recv_displacements[0] = 0;
296 for (int r = 1; r < num_procs; ++r)
297 recv_displacements[r] = recv_displacements[r - 1] + nuclei_counts_per_rank[r - 1];
298
299 int total_count = recv_displacements.back() + nuclei_counts_per_rank.back();
300 gathered_nuclei.resize(total_count);
301 }
302
303 // Step 3: Gather all nuclei into root's buffer
304 MPI_Gatherv(local_nuclei.data(),
305 local_count,
307 gathered_nuclei.data(),
308 nuclei_counts_per_rank.data(),
309 recv_displacements.data(),
311 0,
312 MPI_COMM_WORLD);
313 if (rank == 0)
314 {
315 local_nuclei = gathered_nuclei;
316 }
317}
318
319template <unsigned int dim, unsigned int degree, typename number>
320inline void
322 std::vector<Nucleus<dim>> &local_nuclei)
323{
324 int rank = dealii::Utilities::MPI::this_mpi_process(MPI_COMM_WORLD);
325 int count = local_nuclei.size();
326 MPI_Bcast(&count, 1, MPI_INT, 0, MPI_COMM_WORLD);
327
328 if (rank != 0)
329 {
330 local_nuclei.resize(count);
331 }
332
333 MPI_Bcast(local_nuclei.data(), count, Nucleus<dim>::mpi_datatype(), 0, MPI_COMM_WORLD);
334}
335
336PRISMS_PF_END_NAMESPACE
static dealii::ConditionalOStream & pout_base()
Generic parallel output stream. Used for essential information in release and debug mode.
Definition conditional_ostreams.cc:44
The class handles the stochastic nucleation in PRISMS-PF.
Definition nucleation_manager.h:42
static bool attempt_nucleation(const SolveContext< dim, degree, number > &solve_context, std::vector< Nucleus< dim > > &nuclei)
Main nucleation function. Iterates over the domain and stochastically adds nuclei to the list.
Definition nucleation_manager.h:94
static void mpi_broadcast_nuclei(std::vector< Nucleus< dim > > &local_nuclei)
Broadcasts nuclei lists from root. Modifies.
Definition nucleation_manager.h:321
static unsigned int calculate_number_of_events(const double &rate, const double &delta_t, const double &volume, RNGEngine &rng)
Samples the poisson distribution to calculate a number of events in a time-volume given a rate.
Definition nucleation_manager.h:57
static bool gather_exclude_broadcast_nuclei(std::list< Nucleus< dim > > &new_nuclei_list, std::vector< Nucleus< dim > > &global_nuclei, const UserInputParameters< dim > &user_inputs, const SimulationTimer &time_info)
Gathers the potential new nuclei from each processor onto the root process, eliminates any nuclei tha...
Definition nucleation_manager.h:187
static void mpi_gather_nuclei(std::vector< Nucleus< dim > > &local_nuclei)
Gathers nuclei lists to root. Modifies.
Definition nucleation_manager.h:267
Definition simulation_timer.h:13
unsigned int get_increment() const
Definition simulation_timer.h:23
double get_timestep() const
Definition simulation_timer.h:35
double get_time() const
Definition simulation_timer.h:29
This class provides context for a solver with ptrs to all the relevant dependencies.
Definition solve_context.h:34
const DoFManager< dim, degree > & get_dof_manager() const
Get the dof manager.
Definition solve_context.h:101
const std::vector< FieldAttributes > & get_field_attributes() const
Get the field attributes.
Definition solve_context.h:62
SolutionIndexer< dim, number > & get_solution_indexer() const
Get the solution manager.
Definition solve_context.h:159
const UserInputParameters< dim > & get_user_inputs() const
Get the user-inputs.
Definition solve_context.h:71
const SimulationTimer & get_simulation_timer() const
Get the simulation timer.
Definition solve_context.h:187
const TriangulationManager< dim > & get_triangulation_manager() const
Get the triangulation manager.
Definition solve_context.h:81
static const std::array< const dealii::FESystem< dim >, 2 > fe_systems
Scalar and Vector FE systems.
Definition system_wide.h:29
static const dealii::QGaussLobatto< dim > quadrature
Quadrature rule.
Definition system_wide.h:41
static const dealii::MappingQ1< dim > mapping
Mappings to and from reference cell.
Definition system_wide.h:36
Definition user_input_parameters.h:28
MiscellaneousParameters misc_parameters
Definition user_input_parameters.h:138
NucleationParameters nucleation_parameters
Definition user_input_parameters.h:140
std::mt19937 RNGEngine
Definition miscellaneous_parameters.h:23
Definition conditional_ostreams.cc:20
RNGEngine rng
Definition miscellaneous_parameters.h:66
Struct that holds nucleation parameters.
Definition nucleation_parameters.h:30
unsigned int nucleation_period
Definition nucleation_parameters.h:69
This class contains mutable utilities for phase field problems.
Definition nucleus.h:23
static MPI_Datatype mpi_datatype()
Definition nucleus.h:77
dealii::Point< dim > location
Definition nucleus.h:44