-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Linear elastic FFT solver #441
Conversation
To make this run, explicit instantiation of auto RankFourTensorTempl<T>::operator*(const Tensor<T2> & b) const is required in MOOSE's RankFourTensor.h
4e68f33
to
786951b
Compare
Job Precheck on 786951b wanted to post the following: Your code requires style changes. A patch was auto generated and copied here
Alternatively, with your repository up to date and in the top level of your repository:
|
786951b
to
c799351
Compare
youngs_modulus = 1.0e4 | ||
poissons_ratio = 0.3 | ||
elasticity_tensor_prefactor = young_modulus_function | ||
# Is the line above during the righ thing by scaling the young modulus? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Is the line above during the righ thing by scaling the young modulus? | |
# Is the line above during the right thing by scaling the young modulus? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That comment shouldn't be there anymore...
for (int freq_x = 0; freq_x < ni; ++freq_x) | ||
for (int freq_y = 0; freq_y < nj; ++freq_y) | ||
for (int freq_z = 0; freq_z < nk; ++freq_z) | ||
{ | ||
epsilon_buffer.realSpace()[index] = _initial_strain_tensor; | ||
index++; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about we add
FFTData<T> & operator=(const T & rhs);
which internally uses std::fill
to set the data. This will be more compact and easier to optimize for the compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code in this file would then just be
epsilon_buffer.realSpace() = _initial_strain_tensor;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes it much cleaner. The meaning of the operation becomes more implicit at first sight though.
|
||
const int ni = grid[0]; | ||
const int nj = grid[1]; | ||
const int nk = (grid[2] >> 1) + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should probably be gamma_hat.kTable(2).size()
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. We are mindlessly resizing and filling out the last dimension of _k_table
(should we change this?). In addition, it should not make a difference for this problem as all variables and parameters are defined on the same grid.
// precompute kvector components for current direction
_k_table[i].resize(_grid[i]);
for (int j = 0; j < _grid[i]; ++j)
_k_table[i][j] = 2.0 * libMesh::pi *
((j < (_grid[i] >> 1) + 1) ? Real(j) : (Real(j) - _grid[i])) / _box_size(i);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I am changing it to address other comments
Complex q_square = freq[0] * freq[0] + freq[1] * freq[1] + freq[2] * freq[2]; | ||
if (std::abs(q_square) > 1.0e-12) | ||
{ | ||
gamma_hat.reciprocalSpace()[index](i, j, k, l) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be best to grab a reference to gamma_hat.reciprocalSpace()
outside of the loop.
auto & gamma_hat_F = gamma_hat.reciprocalSpace()
(or whatever naming convention you want to adopt for recoprocal fields)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would just be saving the function call, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A function call in a tight inner loop. As that function is implemented in the header file there is a very good chance the compiler will inline it, however we might as well be explicit about it.
for (int freq_x = 0; freq_x < ni; ++freq_x) | ||
for (int freq_y = 0; freq_y < nj; ++freq_y) | ||
for (int freq_z = 0; freq_z < nk; ++freq_z) | ||
{ | ||
stress_buffer.realSpace()[index] = | ||
elastic_tensor.realSpace()[index] * epsilon_buffer.realSpace()[index]; | ||
index++; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a templated helper function to do these types multiplications in FFTData
template <typename T>
template <typename T1, typename T2>
void FFTData<T>::setToProduct(const FFTData<T1> & m1, const FFTData<T2> & m2)
{
// assert that grid sizes are identical
// loop over indices and multiply
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grid size information is in FFTBuffer
. In FFTData
you have either a real or a complex buffer. I am adding a version of your suggestion.
epsilon_buffer.reciprocalSpace()[index] = _initial_strain_tensor; | ||
else | ||
epsilon_buffer.reciprocalSpace()[index] -= | ||
gamma_hat.reciprocalSpace()[index] * stress_buffer.reciprocalSpace()[index]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this loop we could provide a helper function that takes a Lambda as an argument
template<typename T>
template <typename T1>
FFTData<T>::apply(T1 lambda)
{
size_t index = 0;
for (auto ivec : kTable(0))
for (auto jvec : kTable(1))
for (auto kvec : kTable(2))
_buffer[index++] = lambda(ivec, jvec, kvec, index);
}
(unfortunately FFTData does not have access to the kTable yet, but we can probably store a referece to the FFTBufferBase object that owns the FFTData.)
so the call would look something like
epsilon_buffer.reciprocalSpace().apply([](Real ivec, Real jvec, Real kvec, std::size_t index) {
if (ivec == 0 && jvec == 0 && kvec == 0)
return _initial_strain_tensor;
else
return gamma_hat.reciprocalSpace()[index] * stress_buffer.reciprocalSpace()[index];
} );
Since we are using templating to pass in the lambda the compiler will inline it and should produce very fast code.
Also you ultimately will want to do
epsilon_buffer.reciprocalSpace().apply([](Real ivec, Real jvec, Real kvec, std::size_t index) {
return gamma_hat.reciprocalSpace()[index] * stress_buffer.reciprocalSpace()[index];
} );
epsilon_buffer.setKZero(_initial_strain_tensor);
where setKZero
needs to be implemented (and will simply write to the index that corresponds to the (0,0,0) k-vector. That way you'll write twice to that location but avoid looping over the conditional. This is likely much faster.
for (int freq_z = 0; freq_z < nk; ++freq_z) | ||
{ | ||
stress_buffer.realSpace()[index] = | ||
(elastic_tensor.realSpace()[index]) * epsilon_buffer.realSpace()[index]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above for the setToProduct
function
for (int freq_z = 0; freq_z < nk; ++freq_z) | ||
{ | ||
// Define elastic tensor | ||
std::vector<Real> iso_const(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid creating short lived temporary vectors like the plague. They entail heap allocations and are thus very slow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e. move it out of the loop and since the size is known at compile time we could also think about using arrays (and templating the fillFormVector routines).
stress.reciprocalSpace()[index](2, 2) * freq[2]; | ||
|
||
error_n += kvector_stress[0] * kvector_stress[0] + kvector_stress[1] * kvector_stress[1] + | ||
kvector_stress[1] * kvector_stress[1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a typo? I'd expect some [2]
here
{ | ||
const std::array<Complex, 3> freq{ivec[freq_x] * I, jvec[freq_y] * I, kvec[freq_z] * I}; | ||
|
||
std::array<Complex, 3> kvector_stress; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you used ComplexVectorValue
this would be a lot more compact.
Mostly delegate some buffer operations to FFTData<T>. Use of lambdas.
ComplexVectorValue kvector_stress; | ||
kvector_stress(0) = stress.reciprocalSpace()[index](0, 0) * freq(0) + | ||
stress.reciprocalSpace()[index](1, 0) * freq(1) + | ||
stress.reciprocalSpace()[index](2, 0) * freq(2); | ||
kvector_stress(1) = stress.reciprocalSpace()[index](0, 1) * freq(0) + | ||
stress.reciprocalSpace()[index](1, 1) * freq(1) + | ||
stress.reciprocalSpace()[index](2, 1) * freq(2); | ||
kvector_stress(2) = stress.reciprocalSpace()[index](0, 2) * freq(0) + | ||
stress.reciprocalSpace()[index](1, 2) * freq(1) + | ||
stress.reciprocalSpace()[index](2, 2) * freq(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't that just
kvector_stress = stress.reciprocalSpace()[index] * freq;
??
error_n += kvector_stress(0) * kvector_stress(0) + kvector_stress(1) * kvector_stress(1) + | ||
kvector_stress(2) * kvector_stress(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error_n += kvector_stress(0) * kvector_stress(0) + kvector_stress(1) * kvector_stress(1) + | |
kvector_stress(2) * kvector_stress(2); | |
error_n += kvector_stress.norm_sq(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.norm_sq()
takes the norm of each component's complex number, not what we look for here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it would be the component times its complex conjugate rather than component square? Thats really not what is wanted here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
norm_sq
obtains the norm of each of the components and sums them up. The error formula I am basing this off takes the norm of the entire vector. Results are different.
There is more work to do in terms of convergence checks. The formula implemented here captures stress equilibrium. But we could add BC error and others. We can leave all this for next revision.
for (int i = 0; i < ndim; i++) | ||
for (int j = 0; j < ndim; j++) | ||
error_0 += stress.reciprocalSpace()[0](i, j) * stress.reciprocalSpace()[0](i, j); | ||
error_0 = stress.reciprocalSpace()[0].L2norm() * stress.reciprocalSpace()[0].L2norm(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not to self: add RankTwoTensorTempl<T>::L2norm_sq()
analogous to norm_sq()
src/utils/FFTData.C
Outdated
{ | ||
mooseAssert(grid.size() == 3, "Product defined for 3 dimensions"); | ||
|
||
for (unsigned int index = 0; index < grid[0] * grid[1] * grid[2]; index++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
passing in grid seems unnecessary if you can just evaluate _buffer.size()
This PR aims at solving a periodic small deformation problem with multiple phases using a spectral approach.
Refs. #401