321 lines
11 KiB
C++
321 lines
11 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details
|
|
//
|
|
// -------------------------------------------------------------------
|
|
// Gaussian Random Fields of Matern Covariance for Imperfect Materials
|
|
// -------------------------------------------------------------------
|
|
//
|
|
// See README.md for detailed description.
|
|
//
|
|
// Compile with: make generate_random_field
|
|
//
|
|
// Sample runs:
|
|
// (Basic usage)
|
|
// mpirun -np 4 generate_random_field
|
|
//
|
|
// (Generate 5 particles with random imperfections)
|
|
// mpirun -np 4 generate_random_field -o 1 -r 3 -rp 3 -nu 2 -l1 0.015 -l2 0.015 -l3 0.015 -s 0.01 -t 0.08 -n 5 -pl2 3 -top 0 -rs
|
|
//
|
|
// (Generate an Octet-Truss with random imperfections)
|
|
// mpirun -np 4 generate_random_field -o 1 -r 3 -rp 3 -nu 2 -l1 0.02 -l2 0.02 -l3 0.02 -s 0.01 -t 0.08 -top 1 -rs
|
|
//
|
|
// (Generate an Octet-Truss with random imperfections following a uniform distribution)
|
|
// mpirun -np 4 generate_random_field -o 1 -r 3 -rp 3 -nu 2 -l1 0.02 -l2 0.02 -l3 0.02 -umin 0.01 -umax 0.05 -t 0.08 -top 1 -urf -rs
|
|
//
|
|
// (2D random field with anisotropy)
|
|
// mpirun -np 4 generate_random_field -o 1 -r 3 -rp 3 -nu 4 -l1 0.09 -l2 0.03 -l3 0.05 -s 0.01 -t 0.08 -top 1 -no-rs -m ../../data/ref-square.mesh
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include "mfem.hpp"
|
|
|
|
#include "material_metrics.hpp"
|
|
#include "spde_solver.hpp"
|
|
#include "transformation.hpp"
|
|
#include "util.hpp"
|
|
#include "visualizer.hpp"
|
|
|
|
using namespace std;
|
|
using namespace mfem;
|
|
|
|
enum TopologicalSupport { kParticles, kOctetTruss };
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
#ifdef MFEM_USE_SINGLE
|
|
cout << "This miniapp is not supported in single precision.\n\n";
|
|
return MFEM_SKIP_RETURN_VALUE;
|
|
#endif
|
|
|
|
// 0. Initialize MPI.
|
|
Mpi::Init(argc, argv);
|
|
Hypre::Init();
|
|
|
|
// 1. Parse command-line options.
|
|
const char *mesh_file = "../../data/ref-cube.mesh";
|
|
int order = 1;
|
|
int num_refs = 3;
|
|
int num_parallel_refs = 3;
|
|
int number_of_particles = 3;
|
|
int topological_support = TopologicalSupport::kOctetTruss;
|
|
real_t nu = 2.0;
|
|
real_t tau = 0.08;
|
|
real_t l1 = 0.02;
|
|
real_t l2 = 0.02;
|
|
real_t l3 = 0.02;
|
|
real_t e1 = 0;
|
|
real_t e2 = 0;
|
|
real_t e3 = 0;
|
|
real_t pl1 = 1.0;
|
|
real_t pl2 = 1.0;
|
|
real_t pl3 = 1.0;
|
|
real_t uniform_min = 0.0;
|
|
real_t uniform_max = 1.0;
|
|
real_t offset = 0.0;
|
|
real_t scale = 0.01;
|
|
real_t level_set_threshold = 0.0;
|
|
bool paraview_export = true;
|
|
bool glvis_export = true;
|
|
bool uniform_rf = false;
|
|
bool random_seed = true;
|
|
bool compute_boundary_integrals = false;
|
|
|
|
OptionsParser args(argc, argv);
|
|
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
|
|
args.AddOption(&order, "-o", "--order",
|
|
"Finite element order (polynomial degree) or -1 for"
|
|
" isoparametric space.");
|
|
args.AddOption(&num_refs, "-r", "--refs", "Number of uniform refinements");
|
|
args.AddOption(&num_parallel_refs, "-rp", "--refs-parallel",
|
|
"Number of uniform refinements");
|
|
args.AddOption(&topological_support, "-top", "--topology",
|
|
"Topological support. 0 particles, 1 octet-truss");
|
|
args.AddOption(&nu, "-nu", "--nu", "Fractional exponent nu (smoothness)");
|
|
args.AddOption(&tau, "-t", "--tau", "Parameter for topology generation");
|
|
args.AddOption(&l1, "-l1", "--l1",
|
|
"First component of diagonal core of theta");
|
|
args.AddOption(&l2, "-l2", "--l2",
|
|
"Second component of diagonal core of theta");
|
|
args.AddOption(&l3, "-l3", "--l3",
|
|
"Third component of diagonal core of theta");
|
|
args.AddOption(&e1, "-e1", "--e1", "First euler angle for rotation of theta");
|
|
args.AddOption(&e2, "-e2", "--e2",
|
|
"Second euler angle for rotation of theta");
|
|
args.AddOption(&e3, "-e3", "--e3", "Third euler angle for rotation of theta");
|
|
args.AddOption(&pl1, "-pl1", "--pl1", "Length scale 1 of particles");
|
|
args.AddOption(&pl2, "-pl2", "--pl2", "Length scale 2 of particles");
|
|
args.AddOption(&pl3, "-pl3", "--pl3", "Length scale 3 of particles");
|
|
args.AddOption(&uniform_min, "-umin", "--uniform-min",
|
|
"Minimum value of uniform distribution");
|
|
args.AddOption(&uniform_max, "-umax", "--uniform-max",
|
|
"Maximum value of uniform distribution");
|
|
args.AddOption(&offset, "-off", "--offset",
|
|
"Offset for random field u(x) -> u(x) + a");
|
|
args.AddOption(&scale, "-s", "--scale",
|
|
"Scale for random field u(x) -> a * u(x)");
|
|
args.AddOption(&level_set_threshold, "-lst", "--level-set-threshold",
|
|
"Level set threshold");
|
|
args.AddOption(&number_of_particles, "-n", "--number-of-particles",
|
|
"Number of particles");
|
|
args.AddOption(¶view_export, "-pvis", "--paraview-visualization",
|
|
"-no-pvis", "--no-paraview-visualization",
|
|
"Enable or disable ParaView visualization.");
|
|
args.AddOption(&glvis_export, "-vis", "--visualization", "-no-vis",
|
|
"--no-visualization",
|
|
"Enable or disable GLVis visualization.");
|
|
args.AddOption(&uniform_rf, "-urf", "--uniform-rf", "-no-urf",
|
|
"--no-uniform-rf",
|
|
"Enable or disable the transformation of GRF to URF.");
|
|
args.AddOption(&random_seed, "-rs", "--random-seed", "-no-rs",
|
|
"--no-random-seed", "Enable or disable random seed.");
|
|
args.AddOption(&compute_boundary_integrals, "-cbi",
|
|
"--compute-boundary-integrals", "-no-cbi",
|
|
"--no-compute-boundary-integrals",
|
|
"Enable or disable computation of boundary integrals.");
|
|
args.Parse();
|
|
if (!args.Good())
|
|
{
|
|
args.PrintUsage(cout);
|
|
return 1;
|
|
}
|
|
if (Mpi::Root())
|
|
{
|
|
args.PrintOptions(cout);
|
|
}
|
|
|
|
// 2. Read the mesh from the given mesh file.
|
|
Mesh mesh(mesh_file, 1, 1);
|
|
int dim = mesh.Dimension();
|
|
bool is_3d = (dim == 3);
|
|
|
|
// 3. Refine the mesh to increase the resolution.
|
|
for (int i = 0; i < num_refs; i++)
|
|
{
|
|
mesh.UniformRefinement();
|
|
}
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
mesh.Clear();
|
|
for (int i = 0; i < num_parallel_refs; i++)
|
|
{
|
|
pmesh.UniformRefinement();
|
|
}
|
|
|
|
// 4. Define a finite element space on the mesh.
|
|
H1_FECollection fec(order, dim);
|
|
ParFiniteElementSpace fespace(&pmesh, &fec);
|
|
HYPRE_BigInt size = fespace.GlobalTrueVSize();
|
|
if (Mpi::Root())
|
|
{
|
|
const Array<int> boundary(pmesh.bdr_attributes);
|
|
cout << "Number of finite element unknowns: " << size << "\n";
|
|
cout << "Boundary attributes: ";
|
|
boundary.Print(cout, 6);
|
|
}
|
|
|
|
// ========================================================================
|
|
// II. Generate topological support
|
|
// ========================================================================
|
|
|
|
ParGridFunction v(&fespace);
|
|
v = 0.0;
|
|
MaterialTopology *mdm = nullptr;
|
|
|
|
// II.1 Define the metric for the topological support.
|
|
if (is_3d)
|
|
{
|
|
if (topological_support == TopologicalSupport::kOctetTruss)
|
|
{
|
|
mdm = new OctetTrussTopology();
|
|
}
|
|
else if (topological_support == TopologicalSupport::kParticles)
|
|
{
|
|
// Create the same random particles on all processors.
|
|
std::vector<real_t> random_positions(3 * number_of_particles);
|
|
std::vector<real_t> random_rotations(9 * number_of_particles);
|
|
if (Mpi::Root())
|
|
{
|
|
// Generate random positions and rotations. We generate them on the root
|
|
// process and then broadcast them to all processes because we need the
|
|
// same random positions and rotations on all processes.
|
|
FillWithRandomNumbers(random_positions, 0.2, 0.8);
|
|
FillWithRandomRotations(random_rotations);
|
|
}
|
|
|
|
// Broadcast the random positions and rotations to all processes.
|
|
MPI_Bcast(random_positions.data(), 3 * number_of_particles,
|
|
MPITypeMap<real_t>::mpi_type, 0, MPI_COMM_WORLD);
|
|
MPI_Bcast(random_rotations.data(), 9 * number_of_particles,
|
|
MPITypeMap<real_t>::mpi_type, 0, MPI_COMM_WORLD);
|
|
|
|
mdm = new ParticleTopology(pl1, pl2, pl3, random_positions,
|
|
random_rotations);
|
|
}
|
|
else
|
|
{
|
|
if (Mpi::Root())
|
|
{
|
|
mfem::out << "Error: Selected topological support not valid."
|
|
<< std::endl;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// II.2 Define lambda to wrap the call to the distance metric.
|
|
auto topo = [&mdm, &tau](const Vector &x)
|
|
{
|
|
return (tau - mdm->ComputeMetric(x));
|
|
};
|
|
|
|
// II.3 Create a GridFunction for the topological support.
|
|
FunctionCoefficient topo_coeff(topo);
|
|
v.ProjectCoefficient(topo_coeff);
|
|
}
|
|
|
|
// ========================================================================
|
|
// III. Generate random imperfections via fractional PDE
|
|
// ========================================================================
|
|
|
|
/// III.1 Define the fractional PDE solution
|
|
ParGridFunction u(&fespace);
|
|
u = 0.0;
|
|
|
|
// III.2 Define the boundary conditions.
|
|
spde::Boundary bc;
|
|
if (Mpi::Root())
|
|
{
|
|
bc.PrintInfo();
|
|
bc.VerifyDefinedBoundaries(pmesh);
|
|
}
|
|
|
|
// III.3 Solve the SPDE problem
|
|
spde::SPDESolver solver(nu, bc, &fespace, l1, l2, l3, e1, e2,
|
|
e3);
|
|
const int seed = (random_seed) ? 0 :
|
|
std::numeric_limits<int>::max() - Mpi::WorldRank();
|
|
solver.SetupRandomFieldGenerator(seed);
|
|
solver.GenerateRandomField(u);
|
|
|
|
/// III.4 Verify boundary conditions
|
|
if (compute_boundary_integrals)
|
|
{
|
|
bc.ComputeBoundaryError(u);
|
|
}
|
|
|
|
// ========================================================================
|
|
// III. Combine topological support and random field
|
|
// ========================================================================
|
|
|
|
if (uniform_rf)
|
|
{
|
|
/// Transform the random field to a uniform random field.
|
|
spde::UniformGRFTransformer transformation(uniform_min, uniform_max);
|
|
transformation.Transform(u);
|
|
}
|
|
if (scale != 1.0)
|
|
{
|
|
/// Scale the random field.
|
|
spde::ScaleTransformer transformation(scale);
|
|
transformation.Transform(u);
|
|
}
|
|
if (offset != 0.0)
|
|
{
|
|
/// Add an offset to the random field.
|
|
spde::OffsetTransformer transformation(offset);
|
|
transformation.Transform(u);
|
|
}
|
|
ParGridFunction w(&fespace); // Noisy material field.
|
|
w = 0.0;
|
|
w += u;
|
|
w += v;
|
|
ParGridFunction level_set(w); // Level set field.
|
|
{
|
|
spde::LevelSetTransformer transformation(level_set_threshold);
|
|
transformation.Transform(level_set);
|
|
}
|
|
|
|
// ========================================================================
|
|
// IV. Export visualization to ParaView and GLVis
|
|
// ========================================================================
|
|
|
|
spde::Visualizer vis(pmesh, order, u, v, w, level_set, is_3d);
|
|
if (paraview_export)
|
|
{
|
|
vis.ExportToParaView();
|
|
}
|
|
if (glvis_export)
|
|
{
|
|
vis.SendToGLVis();
|
|
}
|
|
|
|
delete mdm;
|
|
return 0;
|
|
}
|