numerics 0.1.0
Loading...
Searching...
No Matches
field_solver.cpp
Go to the documentation of this file.
1/// @file src/pde/field_solver.cpp
2/// @brief Implementations for FieldSolver and MagneticSolver.
6#include "pde/stencil.hpp"
7#include <algorithm>
8#include <unordered_map>
9
10namespace num {
11
13 const ScalarField3D& source,
14 double tol,
15 int max_iter) {
16 const int nx = phi.nx(), ny = phi.ny(), nz = phi.nz();
17 const double inv_dx2 = 1.0 / (phi.dx() * phi.dx());
18 const idx N = phi.size();
19
20 // RHS = -source on the interior; Dirichlet boundary rows stay 0.
21 Vector b(N, 0.0);
22 for (int k = 1; k < nz - 1; ++k)
23 for (int j = 1; j < ny - 1; ++j)
24 for (int i = 1; i < nx - 1; ++i)
25 b[phi.grid().flat(i, j, k)] = -source(i, j, k);
26
27 // A = -Laplacian (SPD). phi's own storage is the solution vector, so CG
28 // solves in place -- no copy in or out.
30 [&](const Vector& v, Vector& Av) { neg_laplacian_3d(v, Av, nx, ny, nz, inv_dx2); },
31 N));
32 return num::cg(A, b, phi.vec(), tol, static_cast<idx>(max_iter));
33}
34
36 const ScalarField3D& coeff,
37 const std::vector<DirichletBC>& bcs,
38 double tol,
39 int max_iter) {
40 const int nx = phi.nx(), ny = phi.ny(), nz = phi.nz();
41 const idx N = phi.size();
42 const double inv_dx2 = 1.0 / (phi.dx() * phi.dx());
43
44 auto flat = [&](int i, int j, int k) -> idx {
45 return static_cast<idx>(k * ny * nx + j * nx + i);
46 };
47
48 std::unordered_map<int, double> bc_map;
49 bc_map.reserve(bcs.size());
50 for (const auto& e : bcs)
51 bc_map[e.flat_idx] = e.value;
52
53 constexpr int DI[6] = {1, -1, 0, 0, 0, 0};
54 constexpr int DJ[6] = {0, 0, 1, -1, 0, 0};
55 constexpr int DK[6] = {0, 0, 0, 0, 1, -1};
56 constexpr double penalty = 1e10;
57
58 // Symmetric penalty elimination: fold each Dirichlet value into the RHS of
59 // its free neighbours so the operator stays SPD.
60 Vector b(N, 0.0);
61 for (const auto& e : bcs) {
62 b[e.flat_idx] = penalty * e.value;
63 const int ei = e.flat_idx % nx;
64 const int ej = (e.flat_idx / nx) % ny;
65 const int ek = e.flat_idx / (nx * ny);
66 for (int d = 0; d < 6; ++d) {
67 int ni = ei + DI[d], nj = ej + DJ[d], nk = ek + DK[d];
68 if (ni < 0 || ni >= nx || nj < 0 || nj >= ny || nk < 0 || nk >= nz)
69 continue;
70 int nidx = flat(ni, nj, nk);
71 if (bc_map.count(nidx))
72 continue;
73 double sigma_face = 0.5 * (coeff(ei, ej, ek) + coeff(ni, nj, nk));
74 b[nidx] += sigma_face * inv_dx2 * e.value;
75 }
76 }
77
78 auto matvec = [&](const Vector& v, Vector& Av) {
79 for (int k = 0; k < nz; ++k)
80 for (int j = 0; j < ny; ++j)
81 for (int i = 0; i < nx; ++i) {
82 int id = flat(i, j, k);
83 if (bc_map.count(id)) {
84 Av[id] = penalty * v[id];
85 continue;
86 }
87 double Av_ijk = 0.0;
88 for (int d = 0; d < 6; ++d) {
89 int ni = std::max(0, std::min(i + DI[d], nx - 1));
90 int nj = std::max(0, std::min(j + DJ[d], ny - 1));
91 int nk = std::max(0, std::min(k + DK[d], nz - 1));
92 int nidx = flat(ni, nj, nk);
93 double c_face = 0.5 * (coeff(i, j, k) + coeff(ni, nj, nk));
94 double v_nb = bc_map.count(nidx) ? 0.0 : v[nidx];
95 Av_ijk += c_face * inv_dx2 * (v[id] - v_nb);
96 }
97 Av[id] = Av_ijk;
98 }
99 };
100
101 // Solve A phi = b in place; phi's storage is the solution vector.
103 return num::cg(A, b, phi.vec(), tol, static_cast<idx>(max_iter));
104}
105
108 out(phi.nx(), phi.ny(), phi.nz(), phi.dx(), phi.ox(), phi.oy(), phi.oz());
109 gradient_3d(phi, out.x, out.y, out.z);
110 return out;
111}
112
115 out(f.x.nx(), f.x.ny(), f.x.nz(), f.x.dx(), f.x.ox(), f.x.oy(), f.x.oz());
116 divergence_3d(f.x, f.y, f.z, out);
117 return out;
118}
119
121 VectorField3D B(A.x.nx(), A.x.ny(), A.x.nz(), A.x.dx(), A.x.ox(), A.x.oy(), A.x.oz());
122 curl_3d(A.x, A.y, A.z, B.x, B.y, B.z);
123 return B;
124}
125
126// MagneticSolver
127
129 const ScalarField3D& phi) {
131 const int nx = sigma.nx(), ny = sigma.ny(), nz = sigma.nz();
132 for (int k = 0; k < nz; ++k)
133 for (int j = 0; j < ny; ++j)
134 for (int i = 0; i < nx; ++i) {
135 const double neg_s = -sigma(i, j, k);
136 J.x(i, j, k) *= neg_s;
137 J.y(i, j, k) *= neg_s;
138 J.z(i, j, k) *= neg_s;
139 }
140 return J;
141}
142
144 double tol,
145 int max_iter) {
146 const int nx = J.x.nx(), ny = J.x.ny(), nz = J.x.nz();
147 const float dx = J.x.dx(), ox = J.x.ox(), oy = J.x.oy(), oz = J.x.oz();
148
149 auto make_source = [&](const ScalarField3D& Jc) {
150 ScalarField3D src(nx, ny, nz, dx, ox, oy, oz);
151 for (int k = 0; k < nz; ++k)
152 for (int j = 0; j < ny; ++j)
153 for (int i = 0; i < nx; ++i)
154 src(i, j, k) = -MU0 * Jc(i, j, k);
155 return src;
156 };
157
158 ScalarField3D Ax(nx, ny, nz, dx, ox, oy, oz);
159 ScalarField3D Ay(nx, ny, nz, dx, ox, oy, oz);
160 ScalarField3D Az(nx, ny, nz, dx, ox, oy, oz);
161
162 FieldSolver::solve_poisson(Ax, make_source(J.x), tol, max_iter);
163 FieldSolver::solve_poisson(Ay, make_source(J.y), tol, max_iter);
164 FieldSolver::solve_poisson(Az, make_source(J.z), tol, max_iter);
165
166 VectorField3D A(nx, ny, nz, dx, ox, oy, oz);
167 A.x = Ax;
168 A.y = Ay;
169 A.z = Az;
170 return FieldSolver::curl(A);
171}
172
173} // namespace num
BasicVector & vec()
Definition vector.hpp:85
static VectorField3D gradient(const ScalarField3D &phi)
Compute .
static SolverResult solve_var_poisson(ScalarField3D &phi, const ScalarField3D &coeff, const std::vector< DirichletBC > &bcs, double tol=1e-6, int max_iter=500)
Solve with Dirichlet data.
static SolverResult solve_poisson(ScalarField3D &phi, const ScalarField3D &source, double tol=1e-6, int max_iter=500)
Solve with zero Dirichlet boundaries.
static VectorField3D curl(const VectorField3D &A)
Compute .
static ScalarField3D divergence(const VectorField3D &f)
Compute .
static VectorField3D solve_magnetic_field(const VectorField3D &J, double tol=1e-6, int max_iter=500)
static VectorField3D current_density(const ScalarField3D &sigma, const ScalarField3D &phi)
Compute current density J = -sigma*grad(phi) [A/m^2].
static constexpr double MU0
mu_0 [H/m]
float oy() const
Definition field3d.hpp:54
float ox() const
Definition field3d.hpp:53
int nx() const
Definition field3d.hpp:49
float dx() const
Definition field3d.hpp:52
int ny() const
Definition field3d.hpp:50
float oz() const
Definition field3d.hpp:55
int nz() const
Definition field3d.hpp:51
Elliptic solvers and vector calculus on 3D field containers.
CallableOp< F > make_op(F f, idx rows, idx cols)
Definition callable.hpp:39
SPDOp< Op > assume_spd(Op op)
constexpr real phi
Golden ratio.
Definition math.hpp:45
void neg_laplacian_3d(const Vector &x, Vector &y, int nx, int ny, int nz, double inv_dx2)
Compute on a 3D grid.
Definition stencil.hpp:208
void curl_3d(const ScalarField3D &ax, const ScalarField3D &ay, const ScalarField3D &az, ScalarField3D &bx, ScalarField3D &by, ScalarField3D &bz)
Compute with central differences.
Definition stencil.hpp:271
std::size_t idx
Definition types.hpp:11
void matvec(const Matrix &A, const Vector &x, Vector &y, Backend b=default_backend)
y = A * x
Definition matrix.cpp:45
void gradient_3d(const ScalarField3D &phi, ScalarField3D &gx, ScalarField3D &gy, ScalarField3D &gz)
Compute with central differences.
Definition stencil.hpp:233
constexpr real e
Definition math.hpp:44
SolverResult cg(const Matrix &A, const Vector &b, Vector &x, real tol=1e-10, idx max_iter=1000, Backend backend=default_backend)
Definition cg.cpp:8
void divergence_3d(const ScalarField3D &fx, const ScalarField3D &fy, const ScalarField3D &fz, ScalarField3D &out)
Compute with central differences.
Definition stencil.hpp:252
Umbrella include for operator concepts and adapters.
Umbrella include for all linear solvers.
Higher-order stencil and grid-sweep utilities.
ScalarField3D z
Definition field3d.hpp:92
ScalarField3D x
Definition field3d.hpp:92
ScalarField3D y
Definition field3d.hpp:92