Skip to content

Commit 363ae20

Browse files
committed
added mixed BC support for FANS
1 parent 6d8382e commit 363ae20

File tree

10 files changed

+276
-28
lines changed

10 files changed

+276
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## latest
44

5+
- Add support for macroscale mixed stress-strain boundary conditions https://github.com/DataAnalyticsEngineering/FANS/pull/56
56
- Add grain boundary diffusion material model for polycrystals https://github.com/DataAnalyticsEngineering/FANS/pull/52
67
- Add a pixi environment for the FANS_dashboard and some tests https://github.com/DataAnalyticsEngineering/FANS/pull/55
78
- Remove MPI initialization from pyFANS and add an integration test for it https://github.com/DataAnalyticsEngineering/FANS/pull/46

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ set_property(TARGET FANS_FANS PROPERTY PUBLIC_HEADER
177177
include/solverFP.h
178178
include/solver.h
179179
include/setup.h
180+
include/mixedBCs.h
180181

181182
include/material_models/LinearThermal.h
182183
include/material_models/GBDiffusion.h

include/matmodel.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,15 @@ class Matmodel {
4141
virtual void initializeInternalVariables(ptrdiff_t num_elements, int num_gauss_points) {}
4242
virtual void updateInternalVariables() {}
4343

44-
vector<double> macroscale_loading;
44+
vector<double> macroscale_loading;
45+
Matrix<double, n_str, n_str> kapparef_mat; // Reference conductivity matrix
4546

4647
protected:
4748
double l_e_x;
4849
double l_e_y;
4950
double l_e_z;
5051
double v_e;
5152

52-
Matrix<double, n_str, n_str> kapparef_mat; // Reference conductivity matrix
53-
5453
Matrix<double, n_str, howmany * 8> B_el_mean; //!< precomputed mean B matrix over the element
5554
Matrix<double, n_str, howmany * 8> B_int[8]; //!< precomputed B matrix at all integration points
5655
Matrix<double, 8 * n_str, howmany * 8> B;

include/mixedBCs.h

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#ifndef MIXED_BC_H
2+
#define MIXED_BC_H
3+
4+
// ============================================================================
5+
// mixedBCs.h
6+
// --------------------------------------------------------------------------
7+
// • Holds MixedBC + LoadCase structs
8+
// • Provides: MixedBC::from_json(), finalize()
9+
// ============================================================================
10+
11+
#include <Eigen/Dense>
12+
using namespace Eigen;
13+
#include <json.hpp>
14+
using nlohmann::json;
15+
16+
// ---------------------------------------------------------------------------
17+
struct MixedBC {
18+
/* Index sets (0‑based) */
19+
VectorXi idx_E; // strain‑controlled components
20+
VectorXi idx_F; // stress‑controlled components
21+
22+
/* Time paths */
23+
MatrixXd F_E_path; // (#steps × |E|)
24+
MatrixXd P_F_path; // (#steps × |F|)
25+
26+
/* Projectors & auxiliary matrix */
27+
MatrixXd Q_E, Q_F, M; // M = (Q_Fᵀ C0 Q_F)⁻¹
28+
29+
// ------------------------------------------------------------
30+
// build Q_E, Q_F, M
31+
// ------------------------------------------------------------
32+
void finalize(const MatrixXd &C0)
33+
{
34+
const int n_str = static_cast<int>(C0.rows());
35+
36+
Q_E = MatrixXd::Zero(n_str, idx_E.size());
37+
for (int c = 0; c < idx_E.size(); ++c)
38+
Q_E(idx_E(c), c) = 1.0;
39+
40+
Q_F = MatrixXd::Zero(n_str, idx_F.size());
41+
for (int c = 0; c < idx_F.size(); ++c)
42+
Q_F(idx_F(c), c) = 1.0;
43+
44+
if (idx_F.size() > 0)
45+
M = (Q_F.transpose() * C0 * Q_F).inverse();
46+
else
47+
M.resize(0, 0); // pure‑strain case
48+
}
49+
50+
// ------------------------------------------------------------
51+
// Factory: parse MixedBC from a JSON object
52+
// ------------------------------------------------------------
53+
static MixedBC from_json(const json &jc, int n_str)
54+
{
55+
auto toEigen = [](const vector<int> &v) {
56+
VectorXi e(v.size());
57+
for (size_t i = 0; i < v.size(); ++i)
58+
e(static_cast<int>(i)) = v[i];
59+
return e;
60+
};
61+
62+
MixedBC bc;
63+
64+
if (!jc.contains("strain_indices") || !jc.contains("stress_indices"))
65+
throw runtime_error("mixed BC: strain_indices or stress_indices missing");
66+
67+
bc.idx_E = toEigen(jc["strain_indices"].get<vector<int>>());
68+
bc.idx_F = toEigen(jc["stress_indices"].get<vector<int>>());
69+
70+
// ---- sanity: disjoint + complementary -----
71+
vector<char> present(n_str, 0);
72+
for (int i = 0; i < bc.idx_E.size(); ++i) {
73+
int k = bc.idx_E(i);
74+
if (k < 0 || k >= n_str)
75+
throw runtime_error("strain index out of range");
76+
present[k] = 1;
77+
}
78+
for (int i = 0; i < bc.idx_F.size(); ++i) {
79+
int k = bc.idx_F(i);
80+
if (k < 0 || k >= n_str)
81+
throw runtime_error("stress index out of range");
82+
if (present[k])
83+
throw runtime_error("index appears in both strain_indices and stress_indices");
84+
present[k] = 1;
85+
}
86+
for (int k = 0; k < n_str; ++k)
87+
if (!present[k])
88+
throw runtime_error("each component must be either strain‑ or stress‑controlled");
89+
90+
// ---- parse 2‑D arrays (allow empty for |E|==0 etc.) -----
91+
auto get2D = [](const json &arr) {
92+
return arr.get<vector<vector<double>>>();
93+
};
94+
95+
vector<vector<double>> strain_raw, stress_raw;
96+
size_t n_steps = 0;
97+
98+
if (bc.idx_E.size() > 0) {
99+
if (!jc.contains("strain"))
100+
throw runtime_error("strain array missing");
101+
strain_raw = get2D(jc["strain"]);
102+
n_steps = strain_raw.size();
103+
}
104+
if (bc.idx_F.size() > 0) {
105+
if (!jc.contains("stress"))
106+
throw runtime_error("stress array missing");
107+
stress_raw = get2D(jc["stress"]);
108+
n_steps = max(n_steps, stress_raw.size());
109+
}
110+
if (n_steps == 0)
111+
throw runtime_error("mixed BC: at least one of strain/stress must have timesteps");
112+
113+
// default‑fill for missing part (constant 0)
114+
if (strain_raw.empty())
115+
strain_raw.resize(n_steps, vector<double>(0));
116+
if (stress_raw.empty())
117+
stress_raw.resize(n_steps, vector<double>(0));
118+
119+
// consistency checks & build Eigen matrices
120+
bc.F_E_path = MatrixXd::Zero(n_steps, bc.idx_E.size());
121+
bc.P_F_path = MatrixXd::Zero(n_steps, bc.idx_F.size());
122+
123+
for (size_t t = 0; t < n_steps; ++t) {
124+
if (strain_raw[t].size() != static_cast<size_t>(bc.idx_E.size()))
125+
throw runtime_error("strain row length mismatch");
126+
for (int c = 0; c < bc.idx_E.size(); ++c)
127+
bc.F_E_path(static_cast<int>(t), c) = (bc.idx_E.size() ? strain_raw[t][c] : 0.0);
128+
129+
if (stress_raw[t].size() != static_cast<size_t>(bc.idx_F.size()))
130+
throw runtime_error("stress row length mismatch");
131+
for (int c = 0; c < bc.idx_F.size(); ++c)
132+
bc.P_F_path(static_cast<int>(t), c) = (bc.idx_F.size() ? stress_raw[t][c] : 0.0);
133+
}
134+
return bc;
135+
}
136+
};
137+
138+
// ---------------------------------------------------------------------------
139+
// LoadCase : covers both legacy and mixed formats (used by Reader)
140+
// ---------------------------------------------------------------------------
141+
struct LoadCase {
142+
bool mixed = false;
143+
vector<vector<double>> g0_path; // legacy pure‑strain
144+
MixedBC mbc; // mixed BC data
145+
size_t n_steps = 0; // number of time steps
146+
};
147+
148+
// ---------------------------------------------------------------------------
149+
// Helper mix‑in with enable/update for Solver (header‑only)
150+
// ---------------------------------------------------------------------------
151+
template <int howmany>
152+
struct MixedBCController {
153+
bool mixed_active = false;
154+
155+
protected:
156+
MixedBC mbc_local;
157+
size_t step_idx = 0;
158+
VectorXd g0_vec; // current macro strain (size n_str)
159+
160+
// call from user code after v_u update each iteration
161+
template <typename SolverType>
162+
void update(SolverType &solver)
163+
{
164+
if (!mixed_active)
165+
return;
166+
167+
VectorXd Pbar = solver.get_homogenized_stress();
168+
169+
VectorXd PF = (mbc_local.idx_F.size() ? mbc_local.P_F_path.row(step_idx).transpose() : VectorXd());
170+
171+
if (mbc_local.idx_F.size()) {
172+
VectorXd rhs = PF - mbc_local.Q_F.transpose() * Pbar;
173+
VectorXd delta_E = mbc_local.M * rhs;
174+
g0_vec += mbc_local.Q_F * delta_E;
175+
}
176+
177+
vector<double> gvec(g0_vec.data(), g0_vec.data() + g0_vec.size());
178+
solver.matmodel->setGradient(gvec);
179+
}
180+
181+
template <typename SolverType>
182+
void activate(SolverType &solver, const MixedBC &mbc_in, size_t t)
183+
{
184+
mixed_active = true;
185+
mbc_local = mbc_in;
186+
step_idx = t;
187+
mbc_local.finalize(solver.matmodel->kapparef_mat);
188+
189+
int n_str = solver.matmodel->n_str;
190+
g0_vec = VectorXd::Zero(n_str);
191+
if (mbc_local.idx_E.size())
192+
g0_vec += mbc_local.Q_E * mbc_local.F_E_path.row(t).transpose();
193+
194+
vector<double> gvec(g0_vec.data(), g0_vec.data() + n_str);
195+
solver.matmodel->setGradient(gvec);
196+
197+
// Do one update to set the initial strain
198+
solver.updateMixedBC();
199+
}
200+
};
201+
202+
#endif // MIXED_BC_H

include/reader.h

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@
55
#include <map>
66
#include <string>
77
#include <vector>
8+
#include "mixedBCs.h"
89

910
using namespace std;
1011

1112
class Reader {
1213
public:
1314
// contents of input file:
14-
char ms_filename[4096]; // Name of Micro-structure hdf5 file
15-
char ms_datasetname[4096]; // Absolute path of Micro-structure in hdf5 file
16-
char results_prefix[4096];
17-
int n_mat;
18-
json materialProperties;
19-
double TOL;
20-
json errorParameters;
21-
json microstructure;
22-
int n_it;
23-
vector<vector<vector<double>>> g0;
24-
string problemType;
25-
string matmodel;
26-
string method;
15+
char ms_filename[4096]; // Name of Micro-structure hdf5 file
16+
char ms_datasetname[4096]; // Absolute path of Micro-structure in hdf5 file
17+
char results_prefix[4096];
18+
int n_mat;
19+
json materialProperties;
20+
double TOL;
21+
json errorParameters;
22+
json microstructure;
23+
int n_it;
24+
vector<LoadCase> load_cases;
25+
string problemType;
26+
string matmodel;
27+
string method;
2728

2829
vector<string> resultsToWrite;
2930

include/solver.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
typedef Map<Array<double, Dynamic, Dynamic>, Unaligned, OuterStride<>> RealArray;
77

88
template <int howmany>
9-
class Solver {
9+
class Solver : private MixedBCController<howmany> {
1010
public:
1111
Solver(Reader reader, Matmodel<howmany> *matmodel);
1212

@@ -62,6 +62,23 @@ class Solver {
6262
MatrixXd homogenized_tangent;
6363
MatrixXd get_homogenized_tangent(double pert_param);
6464

65+
void enableMixedBC(const MixedBC &mbc, size_t step)
66+
{
67+
this->activate(*this, mbc, step);
68+
}
69+
void disableMixedBC()
70+
{
71+
this->mixed_active = false;
72+
}
73+
bool isMixedBCActive()
74+
{
75+
return this->mixed_active;
76+
}
77+
void updateMixedBC()
78+
{
79+
this->update(*this);
80+
}
81+
6582
protected:
6683
fftw_plan planfft, planifft;
6784
clock_t fft_time, buftime;
@@ -583,6 +600,7 @@ MatrixXd Solver<howmany>::get_homogenized_tangent(double pert_param)
583600
}
584601

585602
matmodel->setGradient(pert_strain);
603+
disableMixedBC();
586604
solve();
587605
perturbed_stress = get_homogenized_stress();
588606

include/solverCG.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ void SolverCG<howmany>::internalSolve()
9393

9494
if (islinear) {
9595
Matrix<double, howmany * 8, 1> res_e;
96+
if (this->isMixedBCActive()) {
97+
// if it is mixed and linear, we need to update the residual
98+
this->updateMixedBC();
99+
this->template compute_residual<2>(v_r_real, v_u_real);
100+
}
96101
this->template compute_residual_basic<0>(rnew_real, d_real,
97102
[&](Matrix<double, howmany * 8, 1> &ue, int mat_index, ptrdiff_t element_idx) -> Matrix<double, howmany * 8, 1> & {
98103
res_e.noalias() = linearModel->phase_stiffness[mat_index] * ue;
@@ -129,6 +134,7 @@ void SolverCG<howmany>::LineSearchSecant()
129134
while (((_iter < MaxIter) && (err > tol))) {
130135

131136
v_u_real += d_real * (alpha_new - alpha_old);
137+
this->updateMixedBC();
132138
this->template compute_residual<0>(rnew_real, v_u_real);
133139
r1pd = dotProduct(rnew_real, d_real);
134140

include/solverFP.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void SolverFP<howmany>::internalSolve()
4444

4545
this->convolution();
4646
v_u_real -= v_r_real;
47+
this->updateMixedBC();
4748
this->template compute_residual<2>(v_r_real, v_u_real);
4849

4950
iter++;

src/main.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,20 @@ void runSolver(Reader &reader, const char *output_file_basename)
1111
{
1212
reader.ReadMS(howmany);
1313

14-
for (size_t load_path_idx = 0; load_path_idx < reader.g0.size(); ++load_path_idx) {
14+
for (size_t load_path_idx = 0; load_path_idx < reader.load_cases.size(); ++load_path_idx) {
1515
Matmodel<howmany> *matmodel = createMatmodel<howmany>(reader);
1616
Solver<howmany> *solver = createSolver(reader, matmodel);
1717

18-
const auto &load_path = reader.g0[load_path_idx];
19-
for (size_t time_step_idx = 0; time_step_idx < load_path.size(); ++time_step_idx) {
20-
const auto &g0 = load_path[time_step_idx];
21-
22-
if (g0.size() != matmodel->n_str) {
23-
throw std::invalid_argument("Invalid length of loading g0");
18+
for (size_t time_step_idx = 0; time_step_idx < reader.load_cases[load_path_idx].n_steps; ++time_step_idx) {
19+
if (reader.load_cases[load_path_idx].mixed) {
20+
solver->enableMixedBC(reader.load_cases[load_path_idx].mbc, time_step_idx);
21+
} else {
22+
const auto &g0 = reader.load_cases[load_path_idx].g0_path[time_step_idx];
23+
matmodel->setGradient(g0);
2424
}
25-
26-
matmodel->setGradient(g0);
2725
solver->solve();
2826
solver->postprocess(reader, output_file_basename, load_path_idx, time_step_idx);
2927
}
30-
3128
delete solver;
3229
delete matmodel;
3330
}

0 commit comments

Comments
 (0)