Compare commits

...
Author SHA1 Message Date
blaz 68f9353cd5 mod 2026-04-01 23:42:36 -07:00
blaz 2bb5f45bb6 mod 2026-04-01 23:39:06 -07:00
blaz cf8743dd5e mod 2026-04-01 23:33:36 -07:00
blaz f5b9c0afeb mod 2026-04-01 23:25:07 -07:00
blaz cf5447d879 mod 2026-04-01 23:20:44 -07:00
bslazarov@github.com 30558cd938 mod 2026-03-31 10:10:51 -07:00
blaz 77511860ef mod 2026-03-26 23:04:50 -07:00
blaz d017504e5a mod 2026-03-26 22:58:50 -07:00
blaz 88fa1d60cf mod 2026-03-26 22:52:21 -07:00
blaz da83d4961a opt 2026-03-26 22:44:05 -07:00
blaz db1e90d02c mod 2026-03-26 21:08:01 -07:00
blaz 010600b18a mod 2026-03-26 20:46:37 -07:00
blaz 613d1a3bcb mod 2026-03-26 20:11:24 -07:00
blaz e07760dfc2 tst grad 2026-03-26 19:20:43 -07:00
blaz acf49b667b explicit time adjoint example 2026-03-26 10:54:59 -07:00
bslazarov@github.com 09ba64bb29 fix 2026-03-24 21:53:59 -07:00
blaz bb31355d93 fix 2026-03-24 19:35:50 -07:00
blaz a483e50abf ch 2026-03-24 19:19:18 -07:00
blaz 2f55b418ed assembly level 2026-03-24 19:02:49 -07:00
blaz 313d74fa1f objective added 2026-03-24 18:55:30 -07:00
blaz 829905c8fc Merge branch 'ode_adjoints' into checkpointing_examples 2026-03-23 21:07:56 -07:00
blaz 272cbe594d Merge branch 'master' into ode_adjoints 2026-03-23 21:07:21 -07:00
blaz e58de8335a changes 2026-03-16 10:22:32 -07:00
blaz a327f612be tst file 2026-03-14 10:32:33 -07:00
blaz fad600cbd0 RK4 adjoint 2026-03-10 22:43:49 -07:00
blaz ec1ae6eaf5 initial not-ready version for RK4 adjoint 2026-03-10 10:52:05 -07:00
blaz 4b4aff736d adjoint struct 2026-02-09 00:23:04 -08:00
blaz 727339e0ae check for objective gradients 2026-02-08 22:07:05 -08:00
blaz d8d4d5b874 Merge branch 'dfem-transpose' into checkpointing_examples 2026-02-08 20:24:10 -08:00
blaz 5a7a017a4e objective integradn 2026-02-08 20:21:33 -08:00
blaz 224aff5ac9 forward part of the adjoint computations 2026-02-07 21:09:30 -08:00
blaz e42762365f adjoint initial version 2026-02-07 20:09:35 -08:00
blaz 765fb4a608 added spectral matrix preconditioner 2026-02-07 19:38:59 -08:00
John Camier 591156be2f Merge branch 'master' into dfem-transpose 2026-01-30 18:42:29 -08:00
blaz 8e2478f964 Checkpointing examples 2026-01-25 19:45:26 -08:00
blaz 06c485bb3e updates 2026-01-21 11:03:43 -08:00
bslazarov 1cd27605f0 memory transfer to Device for q-functions 2026-01-20 14:43:06 -08:00
blaz 547fd02911 anisotrpic elasticity structure 2026-01-20 09:57:29 -08:00
blaz aa92498033 explicit linear elasticity 2026-01-20 09:51:15 -08:00
blaz 024576620a initial REVOLVE 2026-01-16 11:29:25 -08:00
blaz 5b670bda3f file and segement storage 2026-01-15 22:59:32 -08:00
blaz 6b0e135bdb rename files 2026-01-15 22:18:33 -08:00
blaz 204b9df2d6 checkpinting into file - multiple files - one file per snapshot 2026-01-15 22:16:31 -08:00
blaz 8b49379f87 fixed slot in-memory or file storage for checkpointing 2026-01-15 22:15:09 -08:00
blaz d32a00fb2f CMake list 2026-01-15 19:57:10 -08:00
blaz 59c9ca70e4 initial version 2026-01-15 19:49:02 -08:00
camierjs 9646ce4d1a Fix dFEM linearized sections leaks 2026-01-12 17:10:40 -08:00
Julian Andrej f46a81bf3a clearer comment 2026-01-12 13:09:39 -08:00
Julian Andrej 460e5ba483 correct sparsematrix test 2026-01-12 13:09:04 -08:00
Julian Andrej 34593dccac properly initialize matrix pointer 2026-01-09 16:37:16 -08:00
Julian Andrej 248ab78e77 missing dual types 2026-01-09 16:04:01 -08:00
Julian Andrej 6b9bdb11df fix hypreparmatrix assemble with multiple integrators 2026-01-09 13:18:37 -08:00
Julian Andrej 505d2e5123 fix multiple integrators 2026-01-09 12:59:25 -08:00
Julian Andrej 47cff96e2e documentation 2026-01-08 10:57:34 -08:00
Julian Andrej 010f455382 documentation 2026-01-06 08:51:46 -08:00
Julian Andrej acebc322b8 cleanup 2026-01-06 08:43:02 -08:00
Julian Andrej 25ec78df9a make tests more strict 2026-01-05 16:21:58 -08:00
Julian Andrej 8c78387136 remove periodic tests for transposes 2025-12-30 13:40:59 -08:00
Julian Andrej 8684d9d5d1 working 2025-12-30 12:41:29 -08:00
Julian Andrej 2375953e95 debugging state 2025-12-29 13:35:24 -08:00
Julian Andrej ecbb196551 bug 2025-12-23 08:27:01 -08:00
Julian Andrej 90ca758a98 again 2025-12-23 07:40:55 -08:00
Julian Andrej 93d674a7a2 argument order 2025-12-23 07:31:27 -08:00
Julian Andrej 03ec3f739b correct size in parallel for ldof vectors 2025-12-23 07:27:36 -08:00
Julian Andrej a3eb9c8e6b doxygen 2025-12-22 16:30:39 -08:00
Julian Andrej 30f3e123cd more warnings 2025-12-22 16:27:42 -08:00
Julian Andrej 4d4c2325f9 warnings 2025-12-22 16:25:39 -08:00
Julian Andrej db84abedfc make dual numbers work 2025-12-22 16:21:48 -08:00
Julian Andrej 2609e3af6e transpose action using cache 2025-12-22 09:05:50 -08:00
45 changed files with 13042 additions and 118 deletions
+215 -41
View File
@@ -84,29 +84,33 @@ public:
const std::vector<derivative_action_t> &derivative_actions,
const FieldDescriptor &direction,
const int &daction_l_size,
const std::vector<derivative_action_t> &derivative_actions_transpose,
const int &derivative_action_tr_l_size,
const std::vector<derivative_action_t> &derivative_tr_actions,
const FieldDescriptor &transpose_direction,
const int &daction_transpose_l_size,
const std::vector<Vector *> &solutions_l,
const std::vector<Vector *> &parameters_l,
const restriction_callback_t &restriction_callback,
const std::function<void(Vector &, Vector &)> &prolongation_transpose,
const std::function<void(Vector &, Vector &)> &tr_prolongation_transpose,
const std::vector<assemble_derivative_sparsematrix_callback_t>
&assemble_derivative_sparsematrix_callbacks,
const std::vector<assemble_derivative_hypreparmatrix_callback_t>
&assemble_derivative_hypreparmatrix_callbacks) :
const assemble_derivative_hypreparmatrix_callback_t
&assemble_derivative_hypreparmatrix_callback) :
Operator(height, width),
derivative_actions(derivative_actions),
direction(direction),
daction_l(daction_l_size),
daction_l_size(daction_l_size),
derivative_actions_transpose(derivative_actions_transpose),
derivative_action_tr_l_size(derivative_action_tr_l_size),
derivative_tr_actions(derivative_tr_actions),
transpose_direction(transpose_direction),
prolongation_transpose(prolongation_transpose),
tr_prolongation_transpose(tr_prolongation_transpose),
assemble_derivative_sparsematrix_callbacks(
assemble_derivative_sparsematrix_callbacks),
assemble_derivative_hypreparmatrix_callbacks(
assemble_derivative_hypreparmatrix_callbacks)
assemble_derivative_hypreparmatrix_callback(
assemble_derivative_hypreparmatrix_callback)
{
std::vector<Vector> s_l(solutions_l.size());
for (size_t i = 0; i < s_l.size(); i++)
@@ -156,18 +160,18 @@ public:
/// direction_t on T-dofs.
void MultTranspose(const Vector &direction_t, Vector &result_t) const override
{
MFEM_ASSERT(!derivative_actions_transpose.empty(),
MFEM_ASSERT(!derivative_tr_actions.empty(),
"derivative can't be used to be multiplied in transpose mode");
daction_l.SetSize(width);
daction_l.SetSize(derivative_action_tr_l_size);
daction_l = 0.0;
prolongation(transpose_direction, direction_t, direction_l);
for (const auto &f : derivative_actions_transpose)
for (const auto &f : derivative_tr_actions)
{
f(fields_e, direction_l, daction_l);
}
prolongation_transpose(daction_l, result_t);
tr_prolongation_transpose(daction_l, result_t);
};
/// @brief Assemble the derivative operator into a SparseMatrix.
@@ -183,6 +187,10 @@ public:
{
f(fields_e, A);
}
// SparseMatrix A is finalized after all callbacks have contributed to
// it.
A->Finalize();
}
/// @brief Assemble the derivative operator into a HypreParMatrix.
@@ -191,13 +199,7 @@ public:
/// be an uninitialized object.
void Assemble(HypreParMatrix *&A)
{
MFEM_ASSERT(!assemble_derivative_hypreparmatrix_callbacks.empty(),
"derivative can't be assembled into a HypreParMatrix");
for (const auto &f : assemble_derivative_hypreparmatrix_callbacks)
{
f(fields_e, A);
}
assemble_derivative_hypreparmatrix_callback(fields_e, A);
}
private:
@@ -212,10 +214,12 @@ private:
const int daction_l_size;
const int derivative_action_tr_l_size;
/// Transpose Derivative action callbacks. Depending on the requested
/// derivatives in DifferentiableOperator the callbacks represent certain
/// combinations of actions of derivatives of the forward operator.
std::vector<derivative_action_t> derivative_actions_transpose;
std::vector<derivative_action_t> derivative_tr_actions;
FieldDescriptor transpose_direction;
@@ -225,13 +229,15 @@ private:
std::function<void(Vector &, Vector &)> prolongation_transpose;
std::function<void(Vector &, Vector &)> tr_prolongation_transpose;
/// Callbacks that assemble derivatives into a SparseMatrix.
std::vector<assemble_derivative_sparsematrix_callback_t>
assemble_derivative_sparsematrix_callbacks;
/// Callbacks that assemble derivatives into a HypreParMatrix.
std::vector<assemble_derivative_hypreparmatrix_callback_t>
assemble_derivative_hypreparmatrix_callbacks;
assemble_derivative_hypreparmatrix_callback_t
assemble_derivative_hypreparmatrix_callback;
};
/// Class representing a differentiable operator which acts on solution and
@@ -457,7 +463,10 @@ public:
dir_l = s_l[derivative_idx];
}
derivative_setup_callbacks[derivative_id][0](fields_e, dir_l);
for (size_t i = 0; i < derivative_setup_callbacks[derivative_id].size(); i++)
{
derivative_setup_callbacks[derivative_id][i](fields_e, dir_l);
}
return std::make_shared<DerivativeOperator>(
height,
@@ -465,15 +474,17 @@ public:
derivative_action_callbacks[derivative_id],
fields[derivative_idx],
residual_l.Size(),
daction_transpose_callbacks[derivative_id],
derivative_action_tr_l_size[derivative_id],
derivative_action_tr_callbacks[derivative_id],
fields[test_space_field_idx],
GetVSize(fields[test_space_field_idx]),
sol_l,
par_l,
restriction_callback,
prolongation_transpose,
derivative_tr_prolongation_transpose[derivative_id],
assemble_derivative_sparsematrix_callbacks[derivative_id],
assemble_derivative_hypreparmatrix_callbacks[derivative_id]);
assemble_derivative_hypreparmatrix_callback[derivative_id]);
}
private:
@@ -486,13 +497,15 @@ private:
std::map<size_t,
std::vector<derivative_action_t>> derivative_action_callbacks;
std::map<size_t,
std::vector<derivative_action_t>> daction_transpose_callbacks;
std::vector<derivative_action_t>> derivative_action_tr_callbacks;
std::map<size_t,
std::function<void(Vector &, Vector &)>> derivative_tr_prolongation_transpose;
std::map<size_t, int> derivative_action_tr_l_size;
std::map<size_t,
std::vector<assemble_derivative_sparsematrix_callback_t>>
assemble_derivative_sparsematrix_callbacks;
std::map<size_t,
std::vector<assemble_derivative_hypreparmatrix_callback_t>>
assemble_derivative_hypreparmatrix_callbacks;
std::map<size_t, assemble_derivative_hypreparmatrix_callback_t>
assemble_derivative_hypreparmatrix_callback;
std::vector<FieldDescriptor> solutions;
std::vector<FieldDescriptor> parameters;
@@ -510,7 +523,7 @@ private:
std::function<void(Vector &, Vector &)> output_restriction_transpose;
restriction_callback_t restriction_callback;
std::map<size_t, Vector> derivative_qp_caches;
std::map<size_t, std::vector<Vector>> derivative_qp_caches;
std::map<size_t, size_t> assembled_vector_sizes;
@@ -769,9 +782,10 @@ void DifferentiableOperator::AddIntegrator(
auto input_size_on_qp =
get_input_size_on_qp(inputs, std::make_index_sequence<num_inputs> {});
// printf("calculate shmem action info\n");
auto action_shmem_info =
get_shmem_info<entity_t, num_fields, num_inputs, num_outputs>
(input_dtq_maps, output_dtq_maps, fields, num_entities, inputs, num_qp,
(input_dtq_maps, output_dtq_maps, fields, num_entities, num_qp,
input_size_on_qp, residual_size_on_qp, element_dof_ordering);
Vector shmem_cache(action_shmem_info.total_size);
@@ -894,9 +908,10 @@ void DifferentiableOperator::AddIntegrator(
const int da_size_on_qp =
GetSizeOnQP<entity_t>(output_fop, fields[test_space_field_idx]);
// printf("calculate shmem derivative action info\n");
auto shmem_info =
get_shmem_info<entity_t, num_fields, num_inputs, num_outputs>(
input_dtq_maps, output_dtq_maps, fields, num_entities, inputs,
input_dtq_maps, output_dtq_maps, fields, num_entities,
num_qp, input_size_on_qp, residual_size_on_qp,
element_dof_ordering, d_field_idx);
@@ -968,9 +983,12 @@ void DifferentiableOperator::AddIntegrator(
// Quadrature point local derivative cache for each element, with data
// layout:
// [test_vdim, test_op_dim, trial_vdim, trial_op_dim, qp, num_entities].
derivative_qp_caches[derivative_id] = Vector(test_vdim * test_op_dim *
trial_vdim *
total_trial_op_dim * num_qp * num_entities);
derivative_qp_caches[derivative_id].push_back(
Vector(test_vdim * test_op_dim * trial_vdim * total_trial_op_dim * num_qp *
num_entities));
const int cache_index = this->derivative_qp_caches[derivative_id].size() - 1;
// Create local references for MSVC lambda capture compatibility
auto& fields_ref = this->fields;
auto& derivative_qp_caches_ref = this->derivative_qp_caches[derivative_id];
@@ -1012,6 +1030,8 @@ void DifferentiableOperator::AddIntegrator(
trial_vdim,
inputs_trial_op_dim,
qpdc_idx = cache_index,
// capture by ref:
&qpdc_mem = derivative_qp_caches_ref
](std::vector<Vector> &f_e, const Vector &dir_l) mutable
@@ -1024,7 +1044,7 @@ void DifferentiableOperator::AddIntegrator(
shmem_info.direction_size,
num_entities);
auto qpdc = Reshape(qpdc_mem.ReadWrite(), test_vdim, test_op_dim,
auto qpdc = Reshape(qpdc_mem[qpdc_idx].ReadWrite(), test_vdim, test_op_dim,
trial_vdim, total_trial_op_dim, num_qp, num_entities);
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
@@ -1095,6 +1115,7 @@ void DifferentiableOperator::AddIntegrator(
inputs_trial_op_dim,
total_trial_op_dim,
trial_vdim,
qpdc_idx = cache_index,
// capture by ref:
&qpdc_mem = derivative_qp_caches_ref,
&or_transpose
@@ -1112,7 +1133,7 @@ void DifferentiableOperator::AddIntegrator(
shmem_info.direction_size,
num_entities);
auto qpdc = Reshape(qpdc_mem.Read(), test_vdim, test_op_dim,
auto qpdc = Reshape(qpdc_mem[qpdc_idx].Read(), test_vdim, test_op_dim,
trial_vdim, total_trial_op_dim, num_qp, num_entities);
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
@@ -1157,6 +1178,154 @@ void DifferentiableOperator::AddIntegrator(
or_transpose(derivative_action_e, der_action_l);
});
// This prevents Sum/Identity. These are invalid
// as input FieldOperators anyways.
constexpr auto dummy_fop = Value<0> {};
auto [input_rt,
input_e_sz] = get_restriction_transpose<entity_t>
(fields[d_field_idx],
element_dof_ordering, dummy_fop);
const auto input_restriction_transpose = input_rt;
derivative_tr_prolongation_transpose[derivative_id] =
get_prolongation_transpose(
fields[d_field_idx], dummy_fop, mesh.GetComm());
const auto d_tr_field_idx = test_space_field_idx;
const auto direction_tr = fields[d_tr_field_idx];
auto output_size_on_qp =
get_input_size_on_qp(outputs, std::make_index_sequence<num_outputs> {});
const int residual_tr_size_on_qp = trial_vdim * total_trial_op_dim;
auto shmem_tr_info =
get_shmem_info<entity_t, num_fields, num_outputs, num_inputs>(
output_dtq_maps, input_dtq_maps, fields, num_entities,
num_qp, output_size_on_qp, residual_tr_size_on_qp,
element_dof_ordering, test_space_field_idx);
// print_shared_memory_info(shmem_tr_info);
// TODO: this is a hack to extend the shared memory with a known
// offset for a temp variable
Vector shmem_tr_cache(shmem_tr_info.total_size + residual_tr_size_on_qp *
num_qp);
Vector direction_tr_e(get_restriction<entity_t>(
fields[test_space_field_idx],
element_dof_ordering)->Height());
derivative_action_tr_l_size[derivative_id] =
get_restriction<entity_t>(fields[d_field_idx],
element_dof_ordering)->Width();
Vector derivative_action_tr_e(input_e_sz);
derivative_action_tr_e = 0.0;
derivative_action_tr_callbacks[derivative_id].push_back(
[
// capture by copy:
dimension, // int
num_entities, // int
num_trial_dof, // int
num_qp, // int
q1d, // int
test_vdim, // int (= output_fop.vdim)
test_op_dim, // int (derived from output_fop)
inputs, // mfem::future::tuple
outputs, // mfem::future::tuple
attributes, // Array<int>
ir_weights, // DeviceTensor
use_sum_factorization, // bool
input_dtq_maps, // std::array<DofToQuadMap, num_fields>
output_dtq_maps, // std::array<DofToQuadMap, num_fields>
// output_fop, // class derived from FieldOperator
thread_blocks, // ThreadBlocks
shmem_tr_cache, // Vector (local)
shmem_tr_info, // SharedMemoryInfo
// TODO: make this Array<int> a member of the DifferentiableOperator
// and capture it by ref.
elem_attributes, // Array<int>
input_is_dependent,
direction_tr, // FieldDescriptor
direction_tr_e, // Vector
derivative_action_tr_e, // Vector
element_dof_ordering, // ElementDofOrdering
inputs_trial_op_dim,
total_trial_op_dim,
trial_vdim,
input_restriction_transpose,
qpdc_idx = cache_index,
// capture by ref:
&qpdc_mem = derivative_qp_caches_ref
](
std::vector<Vector> &f_e, const Vector &dir_tr_l,
Vector &derivative_action_tr_l) mutable
{
restriction<entity_t>(direction_tr, dir_tr_l, direction_tr_e,
element_dof_ordering);
auto ye = Reshape(derivative_action_tr_e.ReadWrite(), num_trial_dof,
trial_vdim, num_entities);
auto wrapped_fields_e = wrap_fields(f_e, shmem_tr_info.field_sizes,
num_entities);
auto wrapped_direction_e = Reshape(direction_tr_e.ReadWrite(),
shmem_tr_info.direction_size,
num_entities);
auto qpdc = Reshape(qpdc_mem[qpdc_idx].Read(), test_vdim, test_op_dim,
trial_vdim, total_trial_op_dim, num_qp, num_entities);
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
const bool has_attr = attributes.Size() > 0;
const auto d_attr = attributes.Read();
const auto d_elem_attr = elem_attributes->Read();
derivative_action_tr_e = 0.0;
forall([=] MFEM_HOST_DEVICE (int e, real_t *shmem)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
auto [output_dtq_shmem, input_dtq_shmem, fields_shmem,
direction_shmem, input_shmem,
shadow_shmem_, residual_shmem,
scratch_shmem] =
unpack_shmem(shmem, shmem_tr_info, output_dtq_maps, input_dtq_maps,
wrapped_fields_e, wrapped_direction_e, num_qp, e);
auto &shadow_shmem = shadow_shmem_;
std::array<bool, num_outputs> all_true{true};
map_direction_to_quadrature_data_conditional(
shadow_shmem, direction_shmem, output_dtq_shmem, outputs,
ir_weights, scratch_shmem, all_true, dimension,
use_sum_factorization);
auto fhat = Reshape(&residual_shmem(0, 0), trial_vdim,
total_trial_op_dim, num_qp);
auto qpdce = Reshape(&qpdc(0, 0, 0, 0, 0, e), test_vdim, test_op_dim,
trial_vdim, total_trial_op_dim, num_qp);
constexpr bool transpose = true;
apply_qpdc(fhat, shadow_shmem, qpdce, itod, q1d, dimension,
use_sum_factorization, transpose);
auto y = Reshape(&ye(0, 0, e), num_trial_dof, trial_vdim);
auto fi_shmem = Reshape(shmem + shmem_tr_info.total_size, trial_vdim,
total_trial_op_dim, num_qp);
map_quadrature_data_to_fields_conditional(
y, fhat, inputs, itod, input_dtq_shmem, scratch_shmem, fi_shmem,
input_is_dependent, dimension, use_sum_factorization);
}, num_entities, thread_blocks, shmem_tr_info.total_size,
shmem_tr_cache.ReadWrite());
input_restriction_transpose(derivative_action_tr_e, derivative_action_tr_l);
});
assemble_derivative_sparsematrix_callbacks[derivative_id].push_back(
[
// capture by copy:
@@ -1190,7 +1359,7 @@ void DifferentiableOperator::AddIntegrator(
inputs_trial_op_dim,
Ae_mem,
output_to_field,
qpdc_idx = cache_index,
// capture by ref:
&qpdc_mem = derivative_qp_caches_ref,
&fields = fields_ref
@@ -1202,7 +1371,7 @@ void DifferentiableOperator::AddIntegrator(
shmem_info.direction_size,
num_entities);
auto qpdc = Reshape(qpdc_mem.Read(), test_vdim, test_op_dim,
auto qpdc = Reshape(qpdc_mem[qpdc_idx].Read(), test_vdim, test_op_dim,
trial_vdim, total_trial_op_dim, num_qp, num_entities);
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
@@ -1250,7 +1419,10 @@ void DifferentiableOperator::AddIntegrator(
auto test_fes = *std::get_if<const ParFiniteElementSpace *>
(&fields[output_to_field[0]].data);
A = new SparseMatrix(test_fes->GetVSize(), trial_fes->GetVSize());
if (A == nullptr)
{
A = new SparseMatrix(test_fes->GetVSize(), trial_fes->GetVSize());
}
auto tmp = Reshape(Ae_mem.HostReadWrite(), num_test_dof * test_vdim,
num_trial_dof * trial_vdim, num_entities);
@@ -1321,14 +1493,15 @@ void DifferentiableOperator::AddIntegrator(
A->AddSubMatrix(test_vdofs, trial_vdofs, Aee, 1);
}
}
A->Finalize();
// Don't finalize here since multiple callbacks might contribute to the same matrix
// A->Finalize() will be called after all callbacks have contributed
});
// Create local references for MSVC lambda capture compatibility
auto& assemble_derivative_sparsematrix_callbacks_ref =
this->assemble_derivative_sparsematrix_callbacks[derivative_id];
assemble_derivative_hypreparmatrix_callbacks[derivative_id].push_back(
assemble_derivative_hypreparmatrix_callback[derivative_id] =
[
input_is_dependent,
input_to_field,
@@ -1342,6 +1515,7 @@ void DifferentiableOperator::AddIntegrator(
{
f(f_e, spmat);
}
spmat->Finalize();
if (spmat == nullptr)
{
@@ -1395,7 +1569,7 @@ void DifferentiableOperator::AddIntegrator(
trial_fes->Dof_TrueDof_Matrix());
}
delete spmat;
});
};
}, derivative_ids);
}
}
+61
View File
@@ -533,4 +533,65 @@ void map_quadrature_data_to_fields(
}
}
template <size_t N, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_conditional(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const field_operator_ts &fops,
const DeviceTensor<1, const real_t> &op_dims,
const std::array<DofToQuadMap, N> &dtqmaps,
std::array<DeviceTensor<1>, 6> &scratch_mem,
const DeviceTensor<3> &fi_shmem,
const std::array<bool, N> &conditions,
const int &dimension,
const bool &use_sum_factorization)
{
int offset = 0;
for_constexpr<N>([&](auto i)
{
if (conditions[i])
{
[[maybe_unused]] const auto [K, unused, M] = f.GetShape();
const int L = static_cast<int>(op_dims(static_cast<size_t>(i)));
auto fi = Reshape(&fi_shmem(0, 0, 0), K, L, M);
for (int k = 0; k < K; k++)
{
for (int l = 0; l < L; l++)
{
for (int m = 0; m < M; m++)
{
fi(k, l, m) = f(k, l + offset, m);
}
}
}
if (use_sum_factorization)
{
if (dimension == 1)
{
map_quadrature_data_to_fields_tensor_impl_1d(
y, fi, get<i>(fops), dtqmaps[i], scratch_mem);
}
else if (dimension == 2)
{
map_quadrature_data_to_fields_tensor_impl_2d(
y, fi, get<i>(fops), dtqmaps[i], scratch_mem);
}
else if (dimension == 3)
{
map_quadrature_data_to_fields_tensor_impl_3d(
y, fi, get<i>(fops), dtqmaps[i], scratch_mem);
}
else { MFEM_ABORT_KERNEL("dimension not supported"); }
}
else
{
map_quadrature_data_to_fields_impl(y, fi, get<i>(fops), dtqmaps[i]);
}
offset += L;
}
});
}
} // namespace mfem::future
+12 -17
View File
@@ -505,13 +505,13 @@ void map_field_to_quadrature_data(
}
}
template <typename field_operator_ts, size_t num_inputs, size_t num_fields>
template <typename field_operator_ts, size_t N, size_t M>
MFEM_HOST_DEVICE inline
void map_fields_to_quadrature_data(
std::array<DeviceTensor<2>, num_inputs> &fields_qp,
const std::array<DeviceTensor<1>, num_fields> &fields_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
const std::array<size_t, num_inputs> &input_to_field,
std::array<DeviceTensor<2>, N> &fields_qp,
const std::array<DeviceTensor<1>, M> &fields_e,
const std::array<DofToQuadMap, N> &dtqmaps,
const std::array<size_t, N> &input_to_field,
const field_operator_ts &fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
@@ -523,7 +523,7 @@ void map_fields_to_quadrature_data(
// attached to them and we create a dummy field which is not accessed
// inside the functions it is passed to.
const auto dummy_field_weight = DeviceTensor<1>(nullptr, 0);
for_constexpr<num_inputs>([&](auto i)
for_constexpr<N>([&](auto i)
{
const DeviceTensor<1> &field_e =
(input_to_field[i] == SIZE_MAX) ? dummy_field_weight :
@@ -549,12 +549,7 @@ void map_fields_to_quadrature_data(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights, scratch_mem);
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("unsupported dimension");
#endif
}
else { MFEM_ABORT_KERNEL("unsupported dimension"); }
}
else
{
@@ -627,20 +622,20 @@ void map_fields_to_quadrature_data_conditional(
});
}
template <size_t num_inputs, typename field_operator_ts>
template <size_t N, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_direction_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &directions_qp,
std::array<DeviceTensor<2>, N> &directions_qp,
const DeviceTensor<1> &direction_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
const std::array<DofToQuadMap, N> &dtqmaps,
field_operator_ts fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const std::array<bool, num_inputs> &conditions,
const std::array<bool, N> &conditions,
const int &dimension,
const bool &use_sum_factorization)
{
for_constexpr<num_inputs>([&](auto i)
for_constexpr<N>([&](auto i)
{
if (conditions[i])
{
+88 -38
View File
@@ -379,54 +379,102 @@ namespace detail
/// @param shadow_shmem the shadow shared memory.
/// @param qpdc the quadrature point data cache holding the resulting
/// Jacobians on each quadrature point.
/// @param itod inputs trial operator dimension.
/// If input is dependent the value corresponds to the spatial dimension, otherwise
/// a zero indicates non-dependence on the variable.
/// @param op_dims operator dimensions.
/// If an operator is dependent, the value corresponds to the spatial dimension.
/// Otherwise a zero indicates indepence on the variable.
/// @param q the current quadrature point index.
template <size_t num_fields>
/// @param transpose switch to use transpose action.
template <size_t N>
MFEM_HOST_DEVICE inline
void apply_qpdc(
DeviceTensor<3> &fhat,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
const std::array<DeviceTensor<2>, N> &shadow_shmem,
const DeviceTensor<5, const real_t> &qpdc,
const DeviceTensor<1, const real_t> &itod,
const int &q)
const DeviceTensor<1, const real_t> &op_dims,
const int &q,
bool transpose)
{
const size_t num_ops = op_dims.GetShape()[0];
const int test_vdim = qpdc.GetShape()[0];
const int test_op_dim = qpdc.GetShape()[1];
const int trial_vdim = qpdc.GetShape()[2];
const int num_qp = qpdc.GetShape()[4];
const size_t num_inputs = itod.GetShape()[0];
const int total_trial_op_dim = qpdc.GetShape()[3];
for (int i = 0; i < test_vdim; i++)
const int num_qp = qpdc.GetShape()[4];
if (transpose)
{
for (int k = 0; k < test_op_dim; k++)
for (int j = 0; j < trial_vdim; j++)
{
real_t sum = 0.0;
int m_offset = 0;
for (size_t s = 0; s < num_inputs; s++)
for (int m = 0; m < total_trial_op_dim; m++)
{
const int trial_op_dim = static_cast<int>(itod(s));
if (trial_op_dim == 0)
{
continue;
}
const auto d_qp =
Reshape(&(shadow_shmem[s])[0], trial_vdim, trial_op_dim, num_qp);
for (int j = 0; j < trial_vdim; j++)
{
for (int m = 0; m < trial_op_dim; m++)
{
sum += qpdc(i, k, j, m + m_offset, q) * d_qp(j, m, q);
}
}
m_offset += trial_op_dim;
fhat(j, m, q) = 0.0;
}
}
// Since we don't support more than output space right now
// shadow_shmem will always be of size 1.
constexpr int shadow_idx_tr = 0;
auto d_qp = Reshape(&(shadow_shmem[shadow_idx_tr])[0], test_vdim, test_op_dim,
num_qp);
int m_offset = 0;
for (size_t s = 0; s < num_ops; s++)
{
const int trial_op_dim = static_cast<int>(op_dims(s));
if (trial_op_dim == 0) { continue; }
for (int j = 0; j < trial_vdim; j++)
{
for (int m = 0; m < trial_op_dim; m++)
{
real_t sum = 0.0;
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t contrib = qpdc(i, k, j, m + m_offset, q) * d_qp(i, k, q);
sum += contrib;
}
}
fhat(j, m + m_offset, q) += sum;
}
}
m_offset += trial_op_dim;
}
}
else
{
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
real_t sum = 0.0;
int m_offset = 0;
for (size_t s = 0; s < num_ops; s++)
{
const int trial_op_dim = static_cast<int>(op_dims(s));
if (trial_op_dim == 0) { continue; }
const auto d_qp =
Reshape(&(shadow_shmem[s])[0], trial_vdim, trial_op_dim, num_qp);
for (int j = 0; j < trial_vdim; j++)
{
for (int m = 0; m < trial_op_dim; m++)
{
sum += qpdc(i, k, j, m + m_offset, q) * d_qp(j, m, q);
}
}
m_offset += trial_op_dim;
}
fhat(i, k, q) = sum;
}
fhat(i, k, q) = sum;
}
}
}
}
} // namespace detail
/// @brief Apply the quadrature point data cache (qpdc) to a vector
/// (usually a direction).
@@ -445,16 +493,18 @@ void apply_qpdc(
/// @param q1d number of quadrature points in 1D.
/// @param dimension spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
template <size_t num_fields>
/// @param T switch to use transpose application.
template <size_t N>
MFEM_HOST_DEVICE inline
void apply_qpdc(
DeviceTensor<3> &fhat,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
const std::array<DeviceTensor<2>, N> &shadow_shmem,
const DeviceTensor<5, const real_t> &qpdc,
const DeviceTensor<1, const real_t> &itod,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
const bool &use_sum_factorization,
const bool T = false)
{
if (use_sum_factorization)
{
@@ -462,7 +512,7 @@ void apply_qpdc(
{
MFEM_FOREACH_THREAD_DIRECT(q, x, q1d)
{
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
}
}
else if (dimension == 2)
@@ -472,7 +522,7 @@ void apply_qpdc(
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
const int q = qx + q1d * qy;
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
}
}
}
@@ -485,7 +535,7 @@ void apply_qpdc(
MFEM_FOREACH_THREAD_DIRECT(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
}
}
}
@@ -500,7 +550,7 @@ void apply_qpdc(
const int num_qp = qpdc.GetShape()[4];
MFEM_FOREACH_THREAD_DIRECT(q, x, num_qp)
{
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
}
}
}
+29 -16
View File
@@ -243,6 +243,35 @@ void process_qf_arg(
}
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
tensor<T, n> &arg)
{
for (int i = 0; i < n; i++)
{
arg(i) = u(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
tensor<T, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i) = u((i * n) + j);
}
}
}
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(const DeviceTensor<2> &u, arg_type &arg, int qp)
@@ -327,20 +356,4 @@ void process_qf_result(
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
tensor<T, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i) = u((i * n) + j);
}
}
}
} // namespace mfem::future
+9 -3
View File
@@ -1202,7 +1202,14 @@ std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
const Operator *P = get_prolongation(f);
auto PT = [=](const Vector &r_local, Vector &y)
{
P->MultTranspose(r_local, y);
if (P)
{
P->MultTranspose(r_local, y);
}
else
{
y = r_local;
}
};
return PT;
}
@@ -1580,14 +1587,13 @@ struct SharedMemoryInfo
std::array<int, 6> temp_sizes;
};
template <typename entity_t, std::size_t num_fields, std::size_t num_inputs, std::size_t num_outputs, typename input_t>
template <typename entity_t, std::size_t num_fields, std::size_t num_inputs, std::size_t num_outputs>
SharedMemoryInfo<num_fields, num_inputs, num_outputs>
get_shmem_info(
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps,
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
const std::vector<FieldDescriptor> &fields,
const int &num_entities,
const input_t &inputs,
const int &num_qp,
const std::vector<int> &input_size_on_qp,
const int &residual_size_on_qp,
+8
View File
@@ -200,6 +200,10 @@ void OptionsParser::Parse()
isValid = isValidAsInt(argv[i]);
*(int *)(options[j].var_ptr) = atoi(argv[i++]);
break;
case LONG:
isValid = isValidAsInt(argv[i]);
*(long long *)(options[j].var_ptr) = atoi(argv[i++]);
break;
case DOUBLE:
isValid = isValidAsDouble(argv[i]);
*(real_t *)(options[j].var_ptr) = atof(argv[i++]);
@@ -278,6 +282,10 @@ void OptionsParser::WriteValue(const Option &opt, std::ostream &os)
case INT:
os << *(int *)(opt.var_ptr);
break;
case LONG:
os << *(long long *)(opt.var_ptr);
break;
case DOUBLE:
os << *(real_t *)(opt.var_ptr);
+9 -1
View File
@@ -31,7 +31,7 @@ class Vector;
class OptionsParser
{
public:
enum OptionType { INT, DOUBLE, STRING, STD_STRING, ENABLE, DISABLE, ARRAY, VECTOR };
enum OptionType { INT, DOUBLE, STRING, STD_STRING, ENABLE, DISABLE, ARRAY, VECTOR , LONG };
private:
struct Option
@@ -98,6 +98,14 @@ public:
required));
}
/// Add a long integer option and set 'var' to receive the value.
void AddOption(long long *var, const char *short_name, const char *long_name,
const char *description, bool required = false)
{
options.Append(Option(LONG, var, short_name, long_name, description,
required));
}
/// Add a double option and set 'var' to receive the value.
void AddOption(real_t *var, const char *short_name, const char *long_name,
const char *description, bool required = false)
+62 -1
View File
@@ -284,7 +284,7 @@ void RK4Solver::Init(TimeDependentOperator &f_)
int n = f->Width();
y.SetSize(n, mem_type);
k.SetSize(n, mem_type);
z.SetSize(n, mem_type);
z.SetSize(n, mem_type);
}
void RK4Solver::Step(Vector &x, real_t &t, real_t &dt)
@@ -316,6 +316,67 @@ void RK4Solver::Step(Vector &x, real_t &t, real_t &dt)
t += dt;
}
void RK4Solver::SetSolution(const Vector& x, real_t ts)
{
hist.clear(); //we need only the last time step at time ts
StepRecord rec{ts,x};
hist.push_back(rec);
}
void RK4Solver::AdjointStep(Vector &lambda, real_t &t, real_t &dt)
{
MFEM_VERIFY(!hist.empty(), "No forward history.");
MFEM_VERIFY(adj_mode == AdjointMode::Discrete, "Wrong adjoint mode.");
//Restore the solution history, i.e., perform a forward step.
auto rec = hist.back();
hist.pop_back();
const real_t tn = rec.t;
const real_t h = dt;
const Vector &xn = rec.x;
// Recompute forward stage states and stage RHS:
f->SetTime(tn);
f->Mult(xn, k1); // k1
add(xn, h/2.0, k1, y2); // y2
f->SetTime(tn+h/2.0);
f->Mult(y2, k); // k2
add(xn, h/2.0, k, y3); // y3
f->Mult(y3, k); // k3
add(xn, h, k, y4); // y4
//set the initial values for a1,a2,a3,a4
a1.Set(h/6.0,lambda);
a2.Set(h/3.0,lambda);
a3.Set(h/3.0,lambda);
a4.Set(h/6.0,lambda);
//stage 4
f->SetTime(tn+h);
f->JacobianMultTranspose(y4, a4, k);
lambda.Add(1.0,k);
a3.Add(h,k);
//stage 3
f->SetTime(tn+h/2.0);
f->JacobianMultTranspose(y3, a3, k);
lambda.Add(1.0,k);
a2.Add(h/2.0,k);
//stage 2
f->SetTime(tn+h/2.0);
f->JacobianMultTranspose(y2, a2, k);
lambda.Add(1.0,k);
a1.Add(h/2.0,k);
//stage 1
f->SetTime(tn);
f->JacobianMultTranspose(xn, a1, k);
lambda.Add(1.0,k);
}
ExplicitRKSolver::ExplicitRKSolver(int s_, const real_t *a_, const real_t *b_,
const real_t *c_)
{
+135
View File
@@ -190,6 +190,78 @@ public:
while (t < tf) { Step(x, t, dt); }
}
/// @brief The enum defines the supported adjoint modes.
enum class AdjointMode { None, Continuous, Discrete };
AdjointMode adj_mode= AdjointMode::None;
/// Sets the solution at time ts. The solution will be used by the adjoint step
/// to recompute the internal stages/steps between ts and the time t used in the
/// AdjointStep operator.
/// SetSolution(x,ts); and then calling AdjointStep(lambda, t, dt), will
/// first restore the solution/parametrization between ts and t (if required by
/// the time integration scheme) and then execute the adjoint step.
virtual void SetSolution(const Vector& x, real_t ts){}
/// Return primal state x(t) for t in the recorded time interval.
/// Default false => solver does not provide primal interpolation access.
virtual bool GetForwardSolution(real_t t, Vector &x) const
{
return false;
}
/// Perform one backward step for the adjoint variable lambda.
/// dt > 0 requests stepping from t to t - dt.
/// The solver may adjust dt (adaptive / grid snapping) and returns dt used.
/** @param[in,out] lambda Approximate solution.
@param[in,out] t Time associated with the approximate solution @a x.
@param[in,out] dt Time step size.
*/
virtual void AdjointStep(Vector &lambda, real_t &t, real_t &dt)
{
MFEM_ABORT("Adjoint stepping is not supported by this ODESolver.");
}
/// Perform adjoint integration from time @a t [in] to time @a tf [in].
/** @param[in,out] lambda Approximate adjoint solution.
@param[in,out] t Time associated with the approximate solution @a lambda.
@param[in,out] dt Time step size.
@param[in] t0 Requested final time. (t0<t)
*/
virtual void AdjointRun(Vector &lambda, real_t &t, real_t &dt, real_t t0)
{
MFEM_VERIFY(t0 <= t, "AdjointRun expects t0 <= t.");
while (t > t0) {
real_t d = std::min(dt, t - t0);
AdjointStep(lambda, t, d);
dt = d;
}
}
/// Enable adjoint functionality and tell the solver
/// what to record during forward stepping.
/// Default: abort (adjoint mode is not supported).
virtual void EnableAdjoint(AdjointMode mod)
{
if(AdjointMode::None!=mod)
{
MFEM_ABORT("Adjoint is not supported by the ODESolver.");
}
}
/// Return true of the ODESolver supports the AdjointMode
/// and false if not.
virtual bool SupportsAdjoint(AdjointMode mod) const
{
if(AdjointMode::None==mod)
{
return true;
}
//else
return false;
}
/// Returns how many State vectors the ODE requires
virtual int GetStateSize() { return 0; };
@@ -309,10 +381,73 @@ class RK4Solver : public ODESolver
private:
Vector y, k, z;
//storage for the adjoint solver
/// @brief stores the state x at time t
struct StepRecord{
real_t t;
Vector x;
};
/// @brief stores several steps
std::vector<StepRecord> hist;
Vector k1,y2,y3,y4; //RK4 stages
Vector a1,a2,a3,a4; //adjoint vectors
public:
void Init(TimeDependentOperator &f_) override;
void Step(Vector &x, real_t &t, real_t &dt) override;
/// Return true of the ODESolver supports the AdjointMode and false
/// if not.
virtual bool SupportsAdjoint(AdjointMode mod) const override
{
if(AdjointMode::Discrete==mod)
{
return true;
}
//else
return false;
}
/// Enable adjoint functionality and tell the solver
/// what to record during forward stepping.
/// Default: abort (adjoint mode is not supported).
virtual void EnableAdjoint(AdjointMode mod) override
{
if(AdjointMode::Continuous==mod)
{
MFEM_ABORT("Continuous adjoint not supported by the RK4Solver.");
}
adj_mode=mod;
// allocate the storage memeory
if(AdjointMode::Discrete==mod){
int n = f->Width();
k1.SetSize(n, mem_type);
y2.SetSize(n, mem_type);
y3.SetSize(n, mem_type);
y4.SetSize(n, mem_type);
a1.SetSize(n, mem_type);
a2.SetSize(n, mem_type);
a3.SetSize(n, mem_type);
a4.SetSize(n, mem_type);
}
}
void AdjointStep(Vector &lam, real_t &t, real_t &dt) override;
//Two posible ways to restore the histroy
//1) Use Step(Vector &x, real_t &t, real_t &dt) in discrete adjoint mode and record the internal states
//2) Use additional Method SetState(const Vector &x, real_t ts);
// Calling AdjointStep(lam,t,dt) should restore the history between ts and t, and then execute the
// adjoint step in reverse to time t-dt with the recorded discrete states between ts and t.
// Here we will use SetSolution
virtual void SetSolution(const Vector& x, real_t ts) override;
};
+9
View File
@@ -640,6 +640,15 @@ public:
virtual int SUNMassMult(const Vector &x, Vector &v);
virtual ~TimeDependentOperator() { }
/// y = (df/dx(x,t))^T * w
/// Default uses GetGradient(x).MultTranspose(w,y).
virtual void JacobianMultTranspose(const Vector &x,
const Vector &w,
Vector &y) const
{
GetGradient(x).MultTranspose(w, y);
}
};
+2
View File
@@ -31,6 +31,8 @@ add_subdirectory(hdiv-linear-solver)
add_subdirectory(hooke)
add_subdirectory(meshing)
add_subdirectory(mtop)
add_subdirectory(mtop/chpt)
add_subdirectory(mtop/examples)
add_subdirectory(multidomain)
add_subdirectory(nurbs)
add_subdirectory(parelag)
+5
View File
@@ -26,5 +26,10 @@ if(MFEM_USE_MPI AND MFEM_USE_DOUBLE)
MAIN mtop_test_iso_elasticity.cpp
${PAR_MTOP_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(tst_rk4_adj
MAIN tst_rk4_adj.cpp
${PAR_MTOP_COMMON_FILES}
LIBRARIES mfem)
endif (MFEM_USE_MPI AND MFEM_USE_DOUBLE)
+32
View File
@@ -0,0 +1,32 @@
# 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.
if(MFEM_USE_MPI)
list(APPEND AN_SOURCES linear_anisotropic_elasticity.hpp)
list(APPEND AN_HEADERS linear_anisotropic_elasticity.cpp)
convert_filenames_to_full_paths(AN_SOURCES)
convert_filenames_to_full_paths(AN_HEADERS)
set(EX_COMMON_FILES
EXTRA_SOURCES ${AN_SOURCES}
EXTRA_HEADERS ${AN_HEADERS})
add_mfem_miniapp(mtop_test_anisotropic_le
MAIN test_anisotropic_le.cpp
${EX_COMMON_FILES}
LIBRARIES mfem)
endif (MFEM_USE_MPI)
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
# 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.
list(APPEND CHPT_SOURCES )
list(APPEND CHPT_HEADERS )
convert_filenames_to_full_paths(CHPT_SOURCES)
convert_filenames_to_full_paths(CHPT_HEADERS)
set(CHPT_COMMON_FILES
EXTRA_SOURCES ${CHPT_SOURCES}
EXTRA_HEADERS ${CHPT_HEADERS})
add_mfem_miniapp(mtop_test_scalar_chpt
MAIN tst_scalar_field.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_vector_chpt
MAIN tst_vector_field.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_fixedslot
MAIN tst_fixedslot_scalar_vector.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_segmentio
MAIN tst_segmentio_scalar_vector_mfem.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_file
MAIN tst_fileio_scalar_vector_mfem.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_revolve
MAIN revolve.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_dyn
MAIN tst_dynamic_chpt.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_rev_fixedslot
MAIN tst_revolve_chpt_fixedslot.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
@@ -0,0 +1,309 @@
#ifndef MFEM_DYNAMIC_CHECKPOINTING_HPP
#define MFEM_DYNAMIC_CHECKPOINTING_HPP
#include "mfem.hpp"
#include <map>
#include <memory>
#include <limits>
#include <vector>
#include <utility>
namespace mfem
{
template <typename Snapshot>
class InMemoryCheckpointStorage
{
public:
using Handle = int;
Handle InvalidHandle() const { return -1; }
bool IsValid(const Handle &h) const { return h >= 0; }
Handle Store(Snapshot &&snap)
{
Handle id = InvalidHandle();
if (!free_.empty())
{
id = free_.back();
free_.pop_back();
if (slots_[id])
{
// reuse existing allocation
*slots_[id] = std::move(snap);
}
else
{
slots_[id].reset(new Snapshot(std::move(snap)));
}
}
else
{
id = (Handle) slots_.size();
slots_.push_back(std::unique_ptr<Snapshot>(new Snapshot(std::move(snap))));
}
return id;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "InMemoryCheckpointStorage: invalid handle.");
MFEM_VERIFY(h < (Handle) slots_.size(), "InMemoryCheckpointStorage: handle out of range.");
MFEM_VERIFY(slots_[h].get() != nullptr, "InMemoryCheckpointStorage: empty slot.");
f(*slots_[h]);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < (Handle) slots_.size(), "InMemoryCheckpointStorage: handle out of range.");
slots_[h].reset();
free_.push_back(h);
h = InvalidHandle();
}
private:
std::vector<std::unique_ptr<Snapshot>> slots_;
std::vector<Handle> free_;
};
/**
* Dynamic checkpointing manager (WangMoinIaccarino 2009), with pluggable storage.
*
* Snapshot: checkpointed object (often the primal State itself).
* Storage : must provide:
* using Handle = ...
* Handle InvalidHandle() const;
* bool IsValid(const Handle&) const;
* Handle Store(Snapshot&&);
* template<class F> void Read(const Handle&, F&&) const; // calls f(const Snapshot&)
* void Erase(Handle&);
*/
template <typename Snapshot,
typename Storage = InMemoryCheckpointStorage<Snapshot>>
class DynamicCheckpointing
{
public:
using Step = long long;
using Handle = typename Storage::Handle;
struct Checkpoint
{
int level = 0;
Handle h; // InvalidHandle => placeholder
Checkpoint() = default;
Checkpoint(int lvl, const Handle &inv) : level(lvl), h(inv) {}
};
explicit DynamicCheckpointing(int s)
: s_(s),
owned_storage_(new Storage()),
storage_(owned_storage_.get())
{
MFEM_VERIFY(s_ > 0, "DynamicCheckpointing: s must be > 0.");
Reset();
}
DynamicCheckpointing(int s, Storage &external_storage)
: s_(s),
owned_storage_(nullptr),
storage_(&external_storage)
{
MFEM_VERIFY(s_ > 0, "DynamicCheckpointing: s must be > 0.");
Reset();
}
~DynamicCheckpointing() { ReleaseAll(); }
void Reset()
{
ReleaseAll();
cps_.emplace(Step(0), Checkpoint(InfLevel(), storage_->InvalidHandle()));
}
struct CheckpointInfo
{
Step step;
int level;
bool stored;
};
std::vector<CheckpointInfo> GetCheckpointInfo() const
{
std::vector<CheckpointInfo> out;
out.reserve(cps_.size());
for (const auto &kv : cps_)
{
out.push_back({kv.first, kv.second.level, storage_->IsValid(kv.second.h)});
}
return out;
}
Step GetMaxStep() const
{
MFEM_VERIFY(!cps_.empty(), "DynamicCheckpointing: checkpoint map is empty.");
return cps_.rbegin()->first;
}
template <typename State, typename PrimalStepFn, typename MakeSnapshotFn>
void ForwardStep(const Step i,
State &u_i_inout,
PrimalStepFn &&primal_step,
MakeSnapshotFn &&make_snapshot)
{
AllocateCheckpointForNextStep(i);
auto it = cps_.find(i);
if (it != cps_.end())
{
storage_->Erase(it->second.h);
Snapshot snap = make_snapshot(u_i_inout);
it->second.h = storage_->Store(std::move(snap));
}
primal_step(u_i_inout, i);
}
template <typename State,
typename AdjState,
typename PrimalStepFn,
typename AdjointStepFn,
typename MakeSnapshotFn,
typename RestoreSnapshotFn>
void BackwardStep(const Step i,
AdjState &q_ip1_inout,
State &u_work_inout,
PrimalStepFn &&primal_step,
AdjointStepFn &&adjoint_step,
MakeSnapshotFn &&make_snapshot,
RestoreSnapshotFn &&restore_snapshot)
{
// remove placeholder at i+1
const Step ph = i + 1;
auto it_ph = cps_.find(ph);
MFEM_VERIFY(it_ph != cps_.end(),
"DynamicCheckpointing: expected checkpoint at i+1 before BackwardStep.");
storage_->Erase(it_ph->second.h);
cps_.erase(it_ph);
MFEM_ASSERT(GetMaxStep() <= i,
"DynamicCheckpointing: found a checkpoint beyond current adjoint step.");
auto restore_from_handle = [&](Handle &h)
{
storage_->Read(h, [&](const Snapshot &snap)
{
restore_snapshot(snap, u_work_inout);
});
storage_->Erase(h); // retrieved => placeholder (Algorithm 4 semantics)
};
if (GetMaxStep() == i)
{
Handle h = TakeHandleMakePlaceholder(i);
restore_from_handle(h);
}
else
{
const Step k = GetMaxStep();
Handle hk = TakeHandleMakePlaceholder(k);
restore_from_handle(hk);
for (Step t = k; t < i; ++t)
{
ForwardStep(t, u_work_inout, primal_step, make_snapshot);
}
}
adjoint_step(q_ip1_inout, u_work_inout, i);
}
private:
int s_ = 0;
std::unique_ptr<Storage> owned_storage_;
Storage *storage_ = nullptr;
std::map<Step, Checkpoint> cps_;
static int InfLevel() { return std::numeric_limits<int>::max(); }
void ReleaseAll()
{
if (!storage_) { cps_.clear(); return; }
for (auto &kv : cps_) { storage_->Erase(kv.second.h); }
cps_.clear();
}
bool FindDispensableLargestStep(Step &out_step) const
{
int max_level_seen = std::numeric_limits<int>::min();
for (auto it = cps_.rbegin(); it != cps_.rend(); ++it)
{
const Step step = it->first;
const int lvl = it->second.level;
if (max_level_seen > lvl) { out_step = step; return true; }
max_level_seen = (lvl > max_level_seen) ? lvl : max_level_seen;
}
return false;
}
void AllocateCheckpointForNextStep(const Step i)
{
const Step new_step = i + 1;
MFEM_VERIFY(cps_.find(new_step) == cps_.end(),
"DynamicCheckpointing: checkpoint at i+1 already exists.");
const Handle inv = storage_->InvalidHandle();
// allow growth to s+1 entries (incl. placeholder)
if ((int)cps_.size() <= s_)
{
cps_.emplace(new_step, Checkpoint(0, inv));
return;
}
Step disp = -1;
if (FindDispensableLargestStep(disp))
{
auto it = cps_.find(disp);
MFEM_ASSERT(it != cps_.end(), "Internal error: dispensable checkpoint not found.");
storage_->Erase(it->second.h);
cps_.erase(it);
cps_.emplace(new_step, Checkpoint(0, inv));
return;
}
auto it_i = cps_.find(i);
MFEM_VERIFY(it_i != cps_.end(),
"DynamicCheckpointing: promotion expected checkpoint at step i but none found.");
MFEM_VERIFY(i != 0, "DynamicCheckpointing: attempted to remove step 0 checkpoint.");
const int l = it_i->second.level;
storage_->Erase(it_i->second.h);
cps_.erase(it_i);
cps_.emplace(new_step, Checkpoint(l + 1, inv));
}
Handle TakeHandleMakePlaceholder(const Step i)
{
auto it = cps_.find(i);
MFEM_VERIFY(it != cps_.end(),
"DynamicCheckpointing: TakeHandle requested a non-existent checkpoint.");
MFEM_VERIFY(storage_->IsValid(it->second.h),
"DynamicCheckpointing: TakeHandle requested a checkpoint with no snapshot.");
Handle h = std::move(it->second.h);
it->second.h = storage_->InvalidHandle();
return h;
}
};
} // namespace mfem
#endif // MFEM_DYNAMIC_CHECKPOINTING_HPP
@@ -0,0 +1,291 @@
#ifndef MFEM_FILE_CHECKPOINT_STORAGE_HPP
#define MFEM_FILE_CHECKPOINT_STORAGE_HPP
#include "mfem.hpp"
#include <cstdint>
#include <cstdio> // std::remove, std::rename
#include <cstring>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <type_traits>
#if __has_include(<filesystem>)
#include <filesystem>
namespace mfem_fs = std::filesystem;
#define MFEM_HAVE_FILESYSTEM 1
#else
#define MFEM_HAVE_FILESYSTEM 0
#endif
namespace mfem
{
/**
* @brief Default binary serializer for checkpoint snapshots.
*
* Contract:
* - static void Write(std::ostream&, const Snapshot&)
* - static Snapshot Read (std::istream&)
*
* Default implementation supports trivially-copyable POD types.
* Specialization for mfem::Vector is provided below.
*
* Portability note:
* This binary format is NOT portable across endianness or differing sizeof(real_t).
* If you need portability, provide a custom SnapshotIO (e.g., text, XDR, HDF5).
*/
template <typename Snapshot, typename Enable = void>
struct DefaultCheckpointBinaryIO
{
static void Write(std::ostream &, const Snapshot &)
{
static_assert(sizeof(Snapshot) == 0,
"DefaultCheckpointBinaryIO: no implementation for this Snapshot type. "
"Provide a custom SnapshotIO with Write/Read.");
}
static Snapshot Read(std::istream &)
{
static_assert(sizeof(Snapshot) == 0,
"DefaultCheckpointBinaryIO: no implementation for this Snapshot type. "
"Provide a custom SnapshotIO with Write/Read.");
return Snapshot();
}
};
// POD / trivially-copyable types (double, int, structs of POD, etc.)
template <typename Snapshot>
struct DefaultCheckpointBinaryIO<
Snapshot,
typename std::enable_if<std::is_trivially_copyable<Snapshot>::value>::type>
{
static void Write(std::ostream &os, const Snapshot &x)
{
os.write(reinterpret_cast<const char*>(&x), sizeof(Snapshot));
MFEM_VERIFY(os.good(), "DefaultCheckpointBinaryIO: failed to write POD snapshot.");
}
static Snapshot Read(std::istream &is)
{
Snapshot x;
is.read(reinterpret_cast<char*>(&x), sizeof(Snapshot));
MFEM_VERIFY(is.good(), "DefaultCheckpointBinaryIO: failed to read POD snapshot.");
return x;
}
};
// Specialization for mfem::Vector
template <>
struct DefaultCheckpointBinaryIO<mfem::Vector, void>
{
static void Write(std::ostream &os, const mfem::Vector &v)
{
const std::int64_t n = (std::int64_t) v.Size();
os.write(reinterpret_cast<const char*>(&n), sizeof(n));
MFEM_VERIFY(os.good(), "VectorBinaryIO: failed to write vector size.");
if (n > 0)
{
const mfem::real_t *data = v.GetData();
os.write(reinterpret_cast<const char*>(data),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(os.good(), "VectorBinaryIO: failed to write vector data.");
}
}
static mfem::Vector Read(std::istream &is)
{
std::int64_t n = 0;
is.read(reinterpret_cast<char*>(&n), sizeof(n));
MFEM_VERIFY(is.good(), "VectorBinaryIO: failed to read vector size.");
MFEM_VERIFY(n >= 0, "VectorBinaryIO: invalid negative vector size.");
mfem::Vector v((int)n);
if (n > 0)
{
mfem::real_t *data = v.GetData();
is.read(reinterpret_cast<char*>(data),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(is.good(), "VectorBinaryIO: failed to read vector data.");
}
return v;
}
};
/**
* @brief File-based checkpoint storage backend.
*
* Stores each snapshot in a separate file:
* <directory>/<prefix><id><extension>
*
* Handle is a monotonically-increasing integer id (reused if keep_files==false).
*
* Template parameters:
* - Snapshot : stored snapshot type
* - SnapshotIO : serializer with static Write/Read methods (see DefaultCheckpointBinaryIO)
*
* Threading:
* - Not thread-safe. Typical adjoint/checkpointing usage is single-threaded control flow.
*/
template <typename Snapshot,
typename SnapshotIO = DefaultCheckpointBinaryIO<Snapshot>>
class FileCheckpointStorage
{
public:
using Handle = std::int64_t;
/**
* @param directory directory where checkpoint files live (created if create_dir==true)
* @param prefix filename prefix (e.g. "ckpt_")
* @param extension filename extension (e.g. ".bin")
* @param create_dir create directory if missing (requires <filesystem>)
* @param keep_files if true, Erase() will NOT delete files (useful for debugging),
* and ids are NOT reused.
*/
FileCheckpointStorage(const std::string &directory,
const std::string &prefix = "ckpt_",
const std::string &extension = ".bin",
bool create_dir = true,
bool keep_files = false)
: dir_(directory),
prefix_(prefix),
ext_(extension),
keep_files_(keep_files)
{
MFEM_VERIFY(!dir_.empty(), "FileCheckpointStorage: directory must be non-empty.");
MFEM_VERIFY(!prefix_.empty(), "FileCheckpointStorage: prefix must be non-empty.");
MFEM_VERIFY(!ext_.empty(), "FileCheckpointStorage: extension must be non-empty.");
if (create_dir)
{
#if MFEM_HAVE_FILESYSTEM
std::error_code ec;
mfem_fs::create_directories(mfem_fs::path(dir_), ec);
MFEM_VERIFY(!ec, "FileCheckpointStorage: failed to create directory: " << dir_);
#else
MFEM_ABORT("FileCheckpointStorage: create_dir=true requires <filesystem> support.");
#endif
}
}
Handle InvalidHandle() const { return (Handle)-1; }
bool IsValid(const Handle &h) const { return h >= 0; }
/**
* @brief Store snapshot to file, return handle.
*
* Uses atomic-ish pattern:
* - write to "<path>.tmp"
* - rename to "<path>"
*/
Handle Store(Snapshot &&snap)
{
const Handle id = AllocateId_();
const std::string path = Path_(id);
const std::string tmp = path + ".tmp";
{
std::ofstream os(tmp, std::ios::binary | std::ios::trunc);
MFEM_VERIFY(os.is_open(), "FileCheckpointStorage: failed to open for write: " << tmp);
// Write payload
SnapshotIO::Write(os, snap);
MFEM_VERIFY(os.good(), "FileCheckpointStorage: write failed for: " << tmp);
}
// Rename tmp -> final
const int rc = std::rename(tmp.c_str(), path.c_str());
MFEM_VERIFY(rc == 0, "FileCheckpointStorage: rename failed: " << tmp << " -> " << path);
return id;
}
/**
* @brief Read snapshot from file and pass it to callback f(const Snapshot&).
*
* The reference passed to f is valid only during the call.
*/
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "FileCheckpointStorage: Read called with invalid handle.");
const std::string path = Path_(h);
std::ifstream is(path, std::ios::binary);
MFEM_VERIFY(is.is_open(), "FileCheckpointStorage: failed to open for read: " << path);
Snapshot snap = SnapshotIO::Read(is);
MFEM_VERIFY(is.good(), "FileCheckpointStorage: read failed for: " << path);
f(snap);
}
/**
* @brief Erase snapshot (delete file unless keep_files==true), set handle invalid.
*
* Id reuse policy:
* - if keep_files_ == false: deleted ids are reused to avoid creating huge numbers of files
* - if keep_files_ == true : ids are not reused (avoid overwriting old debug files)
*/
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
if (!keep_files_)
{
const std::string path = Path_(h);
const int rc = std::remove(path.c_str());
MFEM_VERIFY(rc == 0, "FileCheckpointStorage: failed to remove file: " << path);
free_.push_back(h);
}
h = InvalidHandle();
}
private:
std::string dir_;
std::string prefix_;
std::string ext_;
bool keep_files_ = false;
mutable Handle next_id_ = 0;
mutable std::vector<Handle> free_;
Handle AllocateId_()
{
if (!keep_files_ && !free_.empty())
{
const Handle id = free_.back();
free_.pop_back();
return id;
}
return next_id_++;
}
std::string Path_(Handle id) const
{
MFEM_VERIFY(IsValid(id), "FileCheckpointStorage: Path_ called with invalid id.");
std::ostringstream oss;
oss << prefix_ << std::setw(12) << std::setfill('0') << id << ext_;
#if MFEM_HAVE_FILESYSTEM
mfem_fs::path p = mfem_fs::path(dir_) / mfem_fs::path(oss.str());
return p.string();
#else
// Fallback: simple concatenation; assumes dir_ ends without trailing slash if needed.
return dir_ + "/" + oss.str();
#endif
}
};
} // namespace mfem
#endif // MFEM_FILE_CHECKPOINT_STORAGE_HPP
@@ -0,0 +1,385 @@
#ifndef MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
#define MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
#include "mfem.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <fstream>
#include <limits>
#include <type_traits>
namespace mfem
{
// ------------------------------------------------------------
// Packer 1: trivially-copyable snapshots (memcpy)
// ------------------------------------------------------------
template <typename Snapshot>
class TrivialFixedPacker
{
public:
TrivialFixedPacker()
{
static_assert(std::is_trivially_copyable<Snapshot>::value,
"TrivialFixedPacker requires Snapshot to be trivially copyable.");
}
std::size_t SlotBytes() const { return sizeof(Snapshot); }
void Pack(const Snapshot &snap, void *dst) const
{
std::memcpy(dst, &snap, sizeof(Snapshot));
}
void Unpack(const void *src, Snapshot &out) const
{
std::memcpy(&out, src, sizeof(Snapshot));
}
};
// ------------------------------------------------------------
// Packer 2: mfem::Vector of fixed length n
// (Serialized size is fixed: n*sizeof(real_t))
// ------------------------------------------------------------
class FixedVectorPacker
{
public:
explicit FixedVectorPacker(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "FixedVectorPacker: n must be > 0.");
}
std::size_t SlotBytes() const
{
return (std::size_t)n_ * sizeof(mfem::real_t);
}
void Pack(const mfem::Vector &v, void *dst) const
{
MFEM_VERIFY(v.Size() == n_, "FixedVectorPacker: vector size mismatch.");
std::memcpy(dst, v.GetData(), SlotBytes());
}
void Unpack(const void *src, mfem::Vector &out) const
{
out.SetSize(n_);
std::memcpy(out.GetData(), src, SlotBytes());
}
int Size() const { return n_; }
private:
int n_ = 0;
};
// ------------------------------------------------------------
// Fixed-slot MEMORY storage: one big RAM block
// ------------------------------------------------------------
template <typename Snapshot, typename Packer = TrivialFixedPacker<Snapshot>>
class FixedSlotMemoryCheckpointStorage
{
public:
using Handle = int;
FixedSlotMemoryCheckpointStorage(int max_slots, const Packer &packer = Packer())
: max_slots_(max_slots), packer_(packer)
{
MFEM_VERIFY(max_slots_ > 0, "FixedSlotMemoryCheckpointStorage: max_slots must be > 0.");
slot_bytes_ = packer_.SlotBytes();
MFEM_VERIFY(slot_bytes_ > 0, "FixedSlotMemoryCheckpointStorage: SlotBytes must be > 0.");
// Single contiguous block
bytes_.resize((std::size_t)max_slots_ * slot_bytes_);
in_use_.assign(max_slots_, 0);
free_.reserve(max_slots_);
for (int i = 0; i < max_slots_; ++i) { free_.push_back(i); }
}
Handle InvalidHandle() const { return -1; }
bool IsValid(const Handle &h) const { return h >= 0; }
int MaxSlots() const { return max_slots_; }
std::size_t SlotBytes() const { return slot_bytes_; }
Handle Store(Snapshot &&snap)
{
MFEM_VERIFY(!free_.empty(),
"FixedSlotMemoryCheckpointStorage: out of slots (increase max_slots).");
const int slot = free_.back();
free_.pop_back();
in_use_[slot] = 1;
void *dst = SlotPtr_(slot);
packer_.Pack(snap, dst);
return slot;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "FixedSlotMemoryCheckpointStorage: Read invalid handle.");
MFEM_VERIFY(h < max_slots_, "FixedSlotMemoryCheckpointStorage: Read handle out of range.");
MFEM_VERIFY(in_use_[h] == 1, "FixedSlotMemoryCheckpointStorage: Read from free slot.");
Snapshot tmp;
const void *src = SlotPtrConst_(h);
packer_.Unpack(src, tmp);
f(tmp);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < max_slots_, "FixedSlotMemoryCheckpointStorage: Erase handle out of range.");
MFEM_VERIFY(in_use_[h] == 1, "FixedSlotMemoryCheckpointStorage: double-free / invalid erase.");
in_use_[h] = 0;
free_.push_back(h);
h = InvalidHandle();
}
/// Optional: return all slots to the free list (does not zero memory).
void Reset()
{
free_.clear();
for (int i = 0; i < max_slots_; ++i) { in_use_[i] = 0; free_.push_back(i); }
}
private:
int max_slots_ = 0;
std::size_t slot_bytes_ = 0;
Packer packer_;
std::vector<unsigned char> bytes_; // single block
std::vector<unsigned char> in_use_;
std::vector<int> free_;
void *SlotPtr_(int slot)
{
return (void*)(&bytes_[(std::size_t)slot * slot_bytes_]);
}
const void *SlotPtrConst_(int slot) const
{
return (const void*)(&bytes_[(std::size_t)slot * slot_bytes_]);
}
};
// ------------------------------------------------------------
// Fixed-slot FILE storage: one single pre-sized file
// ------------------------------------------------------------
template <typename Snapshot, typename Packer = TrivialFixedPacker<Snapshot>>
class FixedSlotFileCheckpointStorage
{
public:
using Handle = int;
struct Header
{
char magic[8]; // "MFCKPTFS"
std::uint64_t version; // 1
std::uint64_t slot_bytes;
std::uint64_t max_slots;
std::uint64_t reserved[4]; // future use / padding
};
FixedSlotFileCheckpointStorage(const std::string &path,
int max_slots,
const Packer &packer = Packer(),
bool truncate = true,
bool flush_on_store = false)
: path_(path),
max_slots_(max_slots),
packer_(packer),
flush_on_store_(flush_on_store)
{
MFEM_VERIFY(!path_.empty(), "FixedSlotFileCheckpointStorage: empty file path.");
MFEM_VERIFY(max_slots_ > 0, "FixedSlotFileCheckpointStorage: max_slots must be > 0.");
slot_bytes_ = packer_.SlotBytes();
MFEM_VERIFY(slot_bytes_ > 0, "FixedSlotFileCheckpointStorage: SlotBytes must be > 0.");
Open_(truncate);
in_use_.assign(max_slots_, 0);
free_.reserve(max_slots_);
for (int i = 0; i < max_slots_; ++i) { free_.push_back(i); }
scratch_.resize(slot_bytes_);
}
~FixedSlotFileCheckpointStorage()
{
if (file_.is_open()) { file_.close(); }
}
Handle InvalidHandle() const { return -1; }
bool IsValid(const Handle &h) const { return h >= 0; }
int MaxSlots() const { return max_slots_; }
std::size_t SlotBytes() const { return slot_bytes_; }
const std::string &Path() const { return path_; }
Handle Store(Snapshot &&snap)
{
MFEM_VERIFY(!free_.empty(),
"FixedSlotFileCheckpointStorage: out of slots (increase max_slots).");
const int slot = free_.back();
free_.pop_back();
in_use_[slot] = 1;
// Pack into scratch buffer then write into fixed slot offset
packer_.Pack(snap, scratch_.data());
const std::uint64_t off = SlotOffset_(slot);
file_.seekp((std::streamoff)off, std::ios::beg);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: seekp failed.");
file_.write(reinterpret_cast<const char*>(scratch_.data()),
(std::streamsize)slot_bytes_);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: write failed.");
if (flush_on_store_) { file_.flush(); }
return slot;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "FixedSlotFileCheckpointStorage: Read invalid handle.");
MFEM_VERIFY(h < max_slots_, "FixedSlotFileCheckpointStorage: Read handle out of range.");
MFEM_VERIFY(in_use_[h] == 1, "FixedSlotFileCheckpointStorage: Read from free slot.");
const std::uint64_t off = SlotOffset_(h);
file_.seekg((std::streamoff)off, std::ios::beg);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: seekg failed.");
file_.read(reinterpret_cast<char*>(scratch_.data()),
(std::streamsize)slot_bytes_);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: read failed.");
Snapshot tmp;
packer_.Unpack(scratch_.data(), tmp);
f(tmp);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < max_slots_, "FixedSlotFileCheckpointStorage: Erase handle out of range.");
MFEM_VERIFY(in_use_[h] == 1, "FixedSlotFileCheckpointStorage: double-free / invalid erase.");
// No file deletion; just return slot to free list.
in_use_[h] = 0;
free_.push_back(h);
h = InvalidHandle();
}
/// Optional: return all slots to free list (file contents remain).
void Reset()
{
free_.clear();
for (int i = 0; i < max_slots_; ++i) { in_use_[i] = 0; free_.push_back(i); }
}
private:
std::string path_;
int max_slots_ = 0;
std::size_t slot_bytes_ = 0;
Packer packer_;
bool flush_on_store_ = false;
// mutable because Read() is const but needs to seek/read
mutable std::fstream file_;
mutable std::vector<unsigned char> scratch_;
std::vector<unsigned char> in_use_;
std::vector<int> free_;
static Header MakeHeader_(std::uint64_t slot_bytes, std::uint64_t max_slots)
{
Header h;
std::memset(&h, 0, sizeof(h));
h.magic[0] = 'M'; h.magic[1] = 'F'; h.magic[2] = 'C'; h.magic[3] = 'K';
h.magic[4] = 'P'; h.magic[5] = 'T'; h.magic[6] = 'F'; h.magic[7] = 'S';
h.version = 1;
h.slot_bytes = slot_bytes;
h.max_slots = max_slots;
return h;
}
void Open_(bool truncate)
{
const std::ios::openmode mode =
std::ios::binary | std::ios::in | std::ios::out | (truncate ? std::ios::trunc : (std::ios::openmode)0);
file_.open(path_.c_str(), mode);
MFEM_VERIFY(file_.is_open(), "FixedSlotFileCheckpointStorage: failed to open file.");
const Header expected = MakeHeader_((std::uint64_t)slot_bytes_, (std::uint64_t)max_slots_);
if (truncate)
{
// Write header
file_.seekp(0, std::ios::beg);
file_.write(reinterpret_cast<const char*>(&expected), sizeof(expected));
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: header write failed.");
// Pre-size file to: header + max_slots*slot_bytes
const std::uint64_t total = (std::uint64_t)sizeof(Header)
+ (std::uint64_t)max_slots_ * (std::uint64_t)slot_bytes_;
MFEM_VERIFY(total > 0, "FixedSlotFileCheckpointStorage: invalid total file size.");
file_.seekp((std::streamoff)(total - 1), std::ios::beg);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: seekp for resize failed.");
const char zero = 0;
file_.write(&zero, 1);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: resize write failed.");
file_.flush();
}
else
{
// Validate existing header
Header got;
file_.seekg(0, std::ios::beg);
file_.read(reinterpret_cast<char*>(&got), sizeof(got));
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: header read failed.");
MFEM_VERIFY(std::memcmp(got.magic, expected.magic, 8) == 0,
"FixedSlotFileCheckpointStorage: magic mismatch.");
MFEM_VERIFY(got.version == expected.version,
"FixedSlotFileCheckpointStorage: version mismatch.");
MFEM_VERIFY(got.slot_bytes == expected.slot_bytes,
"FixedSlotFileCheckpointStorage: slot_bytes mismatch.");
MFEM_VERIFY(got.max_slots == expected.max_slots,
"FixedSlotFileCheckpointStorage: max_slots mismatch.");
}
}
std::uint64_t SlotOffset_(int slot) const
{
return (std::uint64_t)sizeof(Header) + (std::uint64_t)slot * (std::uint64_t)slot_bytes_;
}
};
} // namespace mfem
#endif // MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
+694
View File
@@ -0,0 +1,694 @@
#include "mfem.hpp"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using mfem::out;
// -------------------------------
// Fixed-slot storage: Memory
// -------------------------------
class FixedSlotMemoryStorage
{
private:
int max_slots_ = 0;
size_t slot_bytes_ = 0;
std::vector<uint8_t> data_;
public:
FixedSlotMemoryStorage(int max_slots, size_t slot_bytes)
: max_slots_(max_slots), slot_bytes_(slot_bytes),
data_(size_t(max_slots)*slot_bytes, 0)
{
MFEM_VERIFY(max_slots_ > 0, "max_slots must be > 0");
MFEM_VERIFY(slot_bytes_ > 0, "slot_bytes must be > 0");
}
int MaxSlots() const { return max_slots_; }
size_t SlotBytes() const { return slot_bytes_; }
void Save(int slot, const uint8_t *src, size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch");
MFEM_VERIFY(src != nullptr, "Save: src is null");
std::memcpy(data_.data() + size_t(slot)*slot_bytes_, src, slot_bytes_);
}
void Load(int slot, uint8_t *dst, size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch");
MFEM_VERIFY(dst != nullptr, "Load: dst is null");
std::memcpy(dst, data_.data() + size_t(slot)*slot_bytes_, slot_bytes_);
}
};
// -------------------------------
// Fixed-slot storage: Single file
// -------------------------------
class FixedSlotFileStorage
{
private:
int max_slots_ = 0;
size_t slot_bytes_ = 0;
std::string filename_;
mutable std::fstream file_;
std::streamoff Offset(int slot) const
{
return std::streamoff(size_t(slot) * slot_bytes_);
}
public:
FixedSlotFileStorage(const std::string &filename,
int max_slots,
size_t slot_bytes)
: max_slots_(max_slots), slot_bytes_(slot_bytes), filename_(filename)
{
MFEM_VERIFY(max_slots_ > 0, "max_slots must be > 0");
MFEM_VERIFY(slot_bytes_ > 0, "slot_bytes must be > 0");
MFEM_VERIFY(!filename_.empty(), "filename must not be empty");
// Create/truncate file and size it.
{
std::ofstream ofs(filename_, std::ios::binary | std::ios::trunc);
MFEM_VERIFY(ofs.good(), "Failed to create checkpoint file");
const size_t total = size_t(max_slots_) * slot_bytes_;
if (total > 0)
{
ofs.seekp(std::streamoff(total - 1));
char zero = 0;
ofs.write(&zero, 1);
}
}
file_.open(filename_, std::ios::binary | std::ios::in | std::ios::out);
MFEM_VERIFY(file_.good(), "Failed to open checkpoint file");
}
~FixedSlotFileStorage()
{
if (file_.is_open()) { file_.close(); }
}
int MaxSlots() const { return max_slots_; }
size_t SlotBytes() const { return slot_bytes_; }
void Save(int slot, const uint8_t *src, size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch");
MFEM_VERIFY(src != nullptr, "Save: src is null");
file_.seekp(Offset(slot));
MFEM_VERIFY(file_.good(), "Save: seekp failed");
file_.write(reinterpret_cast<const char*>(src), std::streamsize(slot_bytes_));
MFEM_VERIFY(file_.good(), "Save: write failed");
file_.flush();
}
void Load(int slot, uint8_t *dst, size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch");
MFEM_VERIFY(dst != nullptr, "Load: dst is null");
file_.seekg(Offset(slot));
MFEM_VERIFY(file_.good(), "Load: seekg failed");
file_.read(reinterpret_cast<char*>(dst), std::streamsize(slot_bytes_));
MFEM_VERIFY(file_.good(), "Load: read failed");
}
};
// -------------------------------
// REVOLVE controller (actions)
// (Transcribed from revolve.c used in ADOL-C; Algorithm 799.)
// -------------------------------
enum class RevolveAction
{
advance,
takeshot,
restore,
firsturn,
youturn,
terminate
};
class RevolveController
{
private:
int snaps_ = 0;
int check_ = -1;
int capo_ = 0;
int fine_ = 0;
int turn_ = 0;
int oldfine_ = 0;
int oldsnaps_ = 0;
// Stack of checkpoint times, indexed by 'check_'.
std::vector<int> ch_;
public:
RevolveController() = default;
RevolveController(int snaps, int capo0, int fine0)
: snaps_(snaps), check_(-1), capo_(capo0), fine_(fine0),
turn_(0), oldfine_(fine0), oldsnaps_(snaps), ch_(snaps, 0)
{
MFEM_VERIFY(snaps_ > 0, "REVOLVE snaps must be > 0");
MFEM_VERIFY(capo_ <= fine_, "REVOLVE: capo must be <= fine");
// Match revolve.c initialization behavior.
if (check_ == -1 && capo_ < fine_)
{
turn_ = 0;
ch_[0] = capo_ - 1;
}
}
int Snaps() const { return snaps_; }
int Check() const { return check_; }
int Capo() const { return capo_; }
int Fine() const { return fine_; }
const std::vector<int>& CheckpointTimes() const { return ch_; }
RevolveAction Next()
{
MFEM_VERIFY(!(check_ < -1), "REVOLVE: check < -1");
MFEM_VERIFY(!(capo_ > fine_), "REVOLVE: capo > fine");
if ((check_ == -1) && (capo_ < fine_))
{
turn_ = 0;
ch_[0] = capo_ - 1;
}
const int diff = fine_ - capo_;
switch (diff)
{
case 0:
{
// Terminate or restore to next checkpoint on the stack.
if (check_ == -1 || capo_ == ch_[0])
{
check_ -= 1; // mirror revolve.c behavior
return RevolveAction::terminate;
}
else
{
capo_ = ch_[check_];
oldfine_ = fine_;
return RevolveAction::restore;
}
}
case 1:
{
// One adjoint step available.
fine_ -= 1;
if (check_ >= 0 && ch_[check_] == capo_) { check_ -= 1; }
if (turn_ == 0)
{
turn_ = 1;
oldfine_ = fine_;
return RevolveAction::firsturn;
}
else
{
oldfine_ = fine_;
return RevolveAction::youturn;
}
}
default:
{
// diff > 1
if (check_ == -1 || ch_[check_] != capo_)
{
// Take a new checkpoint at current capo.
check_ += 1;
MFEM_VERIFY(check_ + 1 <= snaps_, "REVOLVE: exceeded snaps");
ch_[check_] = capo_;
oldfine_ = fine_;
return RevolveAction::takeshot;
}
else
{
// Advance capo forward within (capo, fine).
// This follows the binomial logic in revolve.c.
MFEM_VERIFY(!((oldfine_ < fine_) && (snaps_ == check_ + 1)),
"REVOLVE: fine increased unexpectedly with full stack");
const int oldcapo = capo_;
const int ds = snaps_ - check_;
MFEM_VERIFY(ds >= 1, "REVOLVE: ds < 1");
int reps = 0;
long long range = 1;
while (range < (fine_ - capo_))
{
reps += 1;
// range = range*(reps+ds)/reps (integer arithmetic)
range = range * (reps + ds) / reps;
}
MFEM_VERIFY(reps >= 1, "REVOLVE: reps < 1");
// Binomial helper values (integer)
const long long bino1 = range * reps / (ds + reps);
const long long bino2 = (ds > 1) ? (bino1 * ds / (ds + reps - 1)) : 1;
const long long bino3 =
(ds == 1) ? 0 :
(ds > 2) ? (bino2 * (ds - 1) / (ds + reps - 2)) : 1;
const long long bino4 = bino2 * (reps - 1) / ds;
const long long bino5 =
(ds < 3) ? 0 :
(ds > 3) ? (bino3 * (ds - 2) / reps) : 1;
// Kowarz "new version": keep l^ as small as possible
const long long bino6 = bino1 * ds / reps;
const long long gap = fine_ - capo_;
if (gap <= bino1 + bino3)
{
capo_ += int(bino4);
}
else if (gap < bino1 + bino2)
{
capo_ = fine_ - int(bino2 + bino3);
}
else if (gap <= bino1 + bino2 + bino5)
{
capo_ += int(bino1 - bino3);
}
else
{
capo_ = fine_ - int(bino6);
}
if (capo_ == oldcapo) { capo_ = oldcapo + 1; }
oldfine_ = fine_;
return RevolveAction::advance;
}
}
}
}
};
// -------------------------------
// Fixed-step REVOLVE checkpointing
// -------------------------------
template <typename StorageT>
class FixedStepRevolveCheckpointing
{
public:
struct Shot
{
int time = 0; // state index
int slot = 0; // checkpoint slot index
};
private:
int num_steps_ = 0; // total number of primal steps (0..num_steps)
int num_checkpoints_ = 0; // REVOLVE "snaps"
size_t snapshot_bytes_ = 0;
StorageT *storage_ = nullptr;
std::vector<Shot> forward_shots_;
int forward_shot_cursor_ = 0;
// Controller state at the beginning of the reverse sweep (pre-firsturn).
RevolveController ctrl_init_;
RevolveController ctrl_;
// Two scratch buffers for (de)serialization.
std::vector<uint8_t> io_buf_;
std::vector<uint8_t> prefinal_buf_;
bool prefinal_valid_ = false;
// Reverse sweep bookkeeping.
bool reverse_started_ = false;
int u_work_time_ = -1;
public:
FixedStepRevolveCheckpointing(int num_steps,
int num_checkpoints,
size_t snapshot_bytes,
StorageT &storage)
: num_steps_(num_steps),
num_checkpoints_(num_checkpoints),
snapshot_bytes_(snapshot_bytes),
storage_(&storage),
io_buf_(snapshot_bytes, 0),
prefinal_buf_(snapshot_bytes, 0)
{
MFEM_VERIFY(num_steps_ >= 0, "num_steps must be >= 0");
MFEM_VERIFY(num_checkpoints_ > 0, "num_checkpoints must be > 0");
MFEM_VERIFY(snapshot_bytes_ > 0, "snapshot_bytes must be > 0");
MFEM_VERIFY(storage_->MaxSlots() == num_checkpoints_,
"Storage MaxSlots() must match num_checkpoints");
MFEM_VERIFY(storage_->SlotBytes() == snapshot_bytes_,
"Storage SlotBytes() must match snapshot_bytes");
BuildForwardPlanAndInitialControllerState();
Reset();
}
void Reset()
{
forward_shot_cursor_ = 0;
prefinal_valid_ = false;
reverse_started_ = false;
u_work_time_ = -1;
ctrl_ = ctrl_init_;
}
int NumSteps() const { return num_steps_; }
int NumCheckpoints() const { return num_checkpoints_; }
size_t SnapshotBytes() const { return snapshot_bytes_; }
// ForwardStep: called for i=0..num_steps-1
template <typename State, typename PrimalStep, typename MakeSnapshot>
void ForwardStep(int i,
State &u,
PrimalStep &&primal_step,
MakeSnapshot &&make_snapshot)
{
MFEM_VERIFY(0 <= i && i < num_steps_, "ForwardStep: i out of range");
// Take any planned shots at time i (before advancing).
while (forward_shot_cursor_ < (int)forward_shots_.size() &&
forward_shots_[forward_shot_cursor_].time == i)
{
const int slot = forward_shots_[forward_shot_cursor_].slot;
make_snapshot(u, io_buf_.data(), snapshot_bytes_);
storage_->Save(slot, io_buf_.data(), snapshot_bytes_);
forward_shot_cursor_++;
}
// Cache u_{num_steps-1} so reverse can start there (REVOLVE expects capo=num_steps-1).
if (i == num_steps_ - 1)
{
make_snapshot(u, prefinal_buf_.data(), snapshot_bytes_);
prefinal_valid_ = true;
}
// Advance one step.
primal_step(i, u);
}
// BackwardStep: called for i=num_steps-1..0
template <typename State, typename AdjointState,
typename PrimalStep, typename AdjointStep,
typename MakeSnapshot, typename RestoreSnapshot>
void BackwardStep(int i,
AdjointState &lambda,
State &u_work,
PrimalStep &&primal_step,
AdjointStep &&adjoint_step,
MakeSnapshot &&make_snapshot,
RestoreSnapshot &&restore_snapshot)
{
MFEM_VERIFY(0 <= i && i < num_steps_, "BackwardStep: i out of range");
if (!reverse_started_)
{
MFEM_VERIFY(prefinal_valid_ || num_steps_ == 0,
"Reverse started but prefinal state was not captured. "
"Did you run the forward loop through i=num_steps-1?");
ctrl_ = ctrl_init_;
reverse_started_ = true;
if (num_steps_ > 0)
{
// Restore u_{num_steps-1} into u_work.
restore_snapshot(u_work, prefinal_buf_.data(), snapshot_bytes_);
u_work_time_ = num_steps_ - 1;
}
}
// Execute controller actions until we perform exactly one adjoint step.
for (;;)
{
RevolveAction act = ctrl_.Next();
switch (act)
{
case RevolveAction::takeshot:
{
const int slot = ctrl_.Check();
MFEM_VERIFY(u_work_time_ == ctrl_.Capo(),
"takeshot: u_work_time must equal capo");
make_snapshot(u_work, io_buf_.data(), snapshot_bytes_);
storage_->Save(slot, io_buf_.data(), snapshot_bytes_);
break;
}
case RevolveAction::restore:
{
const int slot = ctrl_.Check();
storage_->Load(slot, io_buf_.data(), snapshot_bytes_);
restore_snapshot(u_work, io_buf_.data(), snapshot_bytes_);
u_work_time_ = ctrl_.Capo();
break;
}
case RevolveAction::advance:
{
const int target = ctrl_.Capo();
MFEM_VERIFY(u_work_time_ >= 0, "advance: u_work_time not initialized");
MFEM_VERIFY(target >= u_work_time_, "advance: target < current time");
for (int t = u_work_time_; t < target; ++t)
{
primal_step(t, u_work);
}
u_work_time_ = target;
break;
}
case RevolveAction::firsturn:
case RevolveAction::youturn:
{
// After firsturn/youturn, ctrl_.Fine() has been decremented and equals ctrl_.Capo().
const int step = ctrl_.Fine();
MFEM_VERIFY(step == ctrl_.Capo(), "youturn: fine != capo");
MFEM_VERIFY(step == u_work_time_, "youturn: u_work_time != step");
MFEM_VERIFY(step == i, "BackwardStep called with i that doesn't match REVOLVE schedule");
// One adjoint step.
adjoint_step(step, u_work, lambda);
return;
}
case RevolveAction::terminate:
{
MFEM_ABORT("REVOLVE terminated early: BackwardStep called after completion?");
break;
}
}
}
}
private:
void BuildForwardPlanAndInitialControllerState()
{
forward_shots_.clear();
RevolveController sim(num_checkpoints_, /*capo=*/0, /*fine=*/num_steps_);
// Forward-plan phase: only TAKESHOT/ADVANCE should occur while fine-capo>1.
while (sim.Fine() - sim.Capo() > 1)
{
RevolveAction a = sim.Next();
if (a == RevolveAction::takeshot)
{
forward_shots_.push_back({sim.Capo(), sim.Check()});
}
else if (a == RevolveAction::advance)
{
// nothing to record; capo moved forward inside sim
}
else
{
MFEM_ABORT("Unexpected REVOLVE action during forward planning phase");
}
}
// At this point, sim is in the pre-firsturn state (capo = num_steps-1, fine = num_steps)
// for num_steps>=1. For num_steps<=1, fine-capo<=1 from the start.
ctrl_init_ = sim;
}
};
// ============================================================
// Mini example 1: scalar state (double)
// u_{n+1} = factor * u_n
// J = 0.5 (u_N - target)^2 => lambda_N = (u_N - target)
// lambda_n = factor * lambda_{n+1}
// ============================================================
static void RunScalarExample(int Nsteps, int Ncheck, double factor)
{
out << "\n--- Scalar REVOLVE example ---\n";
out << "Nsteps=" << Nsteps << ", Ncheckpoints=" << Ncheck << ", factor=" << factor << "\n";
const size_t snap_bytes = sizeof(double);
FixedSlotMemoryStorage storage(Ncheck, snap_bytes);
FixedStepRevolveCheckpointing<FixedSlotMemoryStorage> ckpt(Nsteps, Ncheck, snap_bytes, storage);
auto make_snapshot = [](const double &u, uint8_t *outb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double), "scalar snapshot size mismatch");
std::memcpy(outb, &u, sizeof(double));
};
auto restore_snapshot = [](double &u, const uint8_t *inb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double), "scalar snapshot size mismatch");
std::memcpy(&u, inb, sizeof(double));
};
auto primal_step = [factor](int /*step*/, double &u)
{
u *= factor;
};
auto adjoint_step = [factor](int /*step*/, const double & /*u_step*/, double &lambda)
{
lambda *= factor;
};
const double target = 2.0;
// Forward
double u = 1.0;
for (int i = 0; i < Nsteps; ++i)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
}
const double uN = u;
// Init adjoint at final state
double lambda = (uN - target);
// Reverse
double u_work = 0.0; // will be overwritten by restore_snapshot
for (int i = Nsteps - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
}
// Analytic lambda_0 = factor^Nsteps * (uN - target)
double factorN = 1.0;
for (int k = 0; k < Nsteps; ++k) { factorN *= factor; }
const double lambda0_exact = factorN * (uN - target);
out << "uN = " << uN << "\n";
out << "lambda0 (computed) = " << lambda << "\n";
out << "lambda0 (exact) = " << lambda0_exact << "\n";
}
// ============================================================
// Mini example 2: mfem::Vector state
// u_{n+1} = factor * u_n (elementwise scalar multiply)
// J = 0.5 ||u_N - target||^2 => lambda_N = (u_N - target)
// lambda_n = factor * lambda_{n+1}
// ============================================================
static void RunVectorExample(int Nsteps, int Ncheck, int dim, double factor)
{
out << "\n--- mfem::Vector REVOLVE example ---\n";
out << "Nsteps=" << Nsteps << ", Ncheckpoints=" << Ncheck
<< ", dim=" << dim << ", factor=" << factor << "\n";
MFEM_VERIFY(dim > 0, "dim must be > 0");
const size_t snap_bytes = sizeof(double) * size_t(dim);
FixedSlotMemoryStorage storage(Ncheck, snap_bytes);
FixedStepRevolveCheckpointing<FixedSlotMemoryStorage> ckpt(Nsteps, Ncheck, snap_bytes, storage);
auto make_snapshot = [](const mfem::Vector &u, uint8_t *outb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double) * size_t(u.Size()), "Vector snapshot size mismatch");
std::memcpy(outb, u.GetData(), bytes);
};
auto restore_snapshot = [](mfem::Vector &u, const uint8_t *inb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double) * size_t(u.Size()), "Vector snapshot size mismatch");
std::memcpy(u.GetData(), inb, bytes);
};
auto primal_step = [factor](int /*step*/, mfem::Vector &u)
{
u *= factor;
};
auto adjoint_step = [factor](int /*step*/, const mfem::Vector & /*u_step*/, mfem::Vector &lambda)
{
lambda *= factor;
};
mfem::Vector u(dim);
for (int k = 0; k < dim; ++k) { u[k] = 1.0 + 0.1*k; }
mfem::Vector target(dim);
target = 2.0;
// Forward
for (int i = 0; i < Nsteps; ++i)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
}
mfem::Vector uN(u); // copy for reporting
// Init adjoint: lambda_N = uN - target
mfem::Vector lambda(dim);
lambda = u;
lambda -= target;
// Reverse
mfem::Vector u_work(dim);
u_work = 0.0;
for (int i = Nsteps - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
}
out << "||uN||_2 = " << uN.Norml2() << "\n";
out << "||lambda0||_2 = " << lambda.Norml2() << "\n";
}
int main(int argc, char *argv[])
{
mfem::Device device("cpu");
device.Print();
int Nsteps = 20;
int Ncheck = 3;
int dim = 5;
double factor = 1.05;
mfem::OptionsParser args(argc, argv);
args.AddOption(&Nsteps, "-n", "--num-steps", "Number of primal steps.");
args.AddOption(&Ncheck, "-s", "--num-checkpoints", "Number of checkpoints (snaps).");
args.AddOption(&dim, "-d", "--dim", "Vector dimension for the mfem::Vector example.");
args.AddOption(&factor, "-f", "--factor", "Scalar factor in the toy update u_{n+1}=f*u_n.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(out);
return 1;
}
args.PrintOptions(out);
RunScalarExample(Nsteps, Ncheck, factor);
RunVectorExample(Nsteps, Ncheck, dim, factor);
return 0;
}
@@ -0,0 +1,535 @@
#ifndef MFEM_REVOLVE_CHECKPOINTING_HPP
#define MFEM_REVOLVE_CHECKPOINTING_HPP
#include "mfem.hpp"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using mfem::out;
// -------------------------------
// Fixed-slot storage: Memory
// -------------------------------
class FixedSlotMemoryStorage
{
private:
int max_slots_ = 0;
size_t slot_bytes_ = 0;
std::vector<uint8_t> data_;
public:
FixedSlotMemoryStorage(int max_slots, size_t slot_bytes)
: max_slots_(max_slots), slot_bytes_(slot_bytes),
data_(size_t(max_slots)*slot_bytes, 0)
{
MFEM_VERIFY(max_slots_ > 0, "max_slots must be > 0");
MFEM_VERIFY(slot_bytes_ > 0, "slot_bytes must be > 0");
}
int MaxSlots() const { return max_slots_; }
size_t SlotBytes() const { return slot_bytes_; }
void Save(int slot, const uint8_t *src, size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch");
MFEM_VERIFY(src != nullptr, "Save: src is null");
std::memcpy(data_.data() + size_t(slot)*slot_bytes_, src, slot_bytes_);
}
void Load(int slot, uint8_t *dst, size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch");
MFEM_VERIFY(dst != nullptr, "Load: dst is null");
std::memcpy(dst, data_.data() + size_t(slot)*slot_bytes_, slot_bytes_);
}
};
// -------------------------------
// Fixed-slot storage: Single file
// -------------------------------
class FixedSlotFileStorage
{
private:
int max_slots_ = 0;
size_t slot_bytes_ = 0;
std::string filename_;
mutable std::fstream file_;
std::streamoff Offset(int slot) const
{
return std::streamoff(size_t(slot) * slot_bytes_);
}
public:
FixedSlotFileStorage(const std::string &filename,
int max_slots,
size_t slot_bytes)
: max_slots_(max_slots), slot_bytes_(slot_bytes), filename_(filename)
{
MFEM_VERIFY(max_slots_ > 0, "max_slots must be > 0");
MFEM_VERIFY(slot_bytes_ > 0, "slot_bytes must be > 0");
MFEM_VERIFY(!filename_.empty(), "filename must not be empty");
// Create/truncate file and size it.
{
std::ofstream ofs(filename_, std::ios::binary | std::ios::trunc);
MFEM_VERIFY(ofs.good(), "Failed to create checkpoint file");
const size_t total = size_t(max_slots_) * slot_bytes_;
if (total > 0)
{
ofs.seekp(std::streamoff(total - 1));
char zero = 0;
ofs.write(&zero, 1);
}
}
file_.open(filename_, std::ios::binary | std::ios::in | std::ios::out);
MFEM_VERIFY(file_.good(), "Failed to open checkpoint file");
}
~FixedSlotFileStorage()
{
if (file_.is_open()) { file_.close(); }
}
int MaxSlots() const { return max_slots_; }
size_t SlotBytes() const { return slot_bytes_; }
void Save(int slot, const uint8_t *src, size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch");
MFEM_VERIFY(src != nullptr, "Save: src is null");
file_.seekp(Offset(slot));
MFEM_VERIFY(file_.good(), "Save: seekp failed");
file_.write(reinterpret_cast<const char*>(src), std::streamsize(slot_bytes_));
MFEM_VERIFY(file_.good(), "Save: write failed");
file_.flush();
}
void Load(int slot, uint8_t *dst, size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch");
MFEM_VERIFY(dst != nullptr, "Load: dst is null");
file_.seekg(Offset(slot));
MFEM_VERIFY(file_.good(), "Load: seekg failed");
file_.read(reinterpret_cast<char*>(dst), std::streamsize(slot_bytes_));
MFEM_VERIFY(file_.good(), "Load: read failed");
}
};
// -------------------------------
// REVOLVE controller (actions)
// (Transcribed from revolve.c used in ADOL-C; Algorithm 799.)
// -------------------------------
enum class RevolveAction
{
advance,
takeshot,
restore,
firsturn,
youturn,
terminate
};
class RevolveController
{
private:
int snaps_ = 0;
int check_ = -1;
int capo_ = 0;
int fine_ = 0;
int turn_ = 0;
int oldfine_ = 0;
int oldsnaps_ = 0;
// Stack of checkpoint times, indexed by 'check_'.
std::vector<int> ch_;
public:
RevolveController() = default;
RevolveController(int snaps, int capo0, int fine0)
: snaps_(snaps), check_(-1), capo_(capo0), fine_(fine0),
turn_(0), oldfine_(fine0), oldsnaps_(snaps), ch_(snaps, 0)
{
MFEM_VERIFY(snaps_ > 0, "REVOLVE snaps must be > 0");
MFEM_VERIFY(capo_ <= fine_, "REVOLVE: capo must be <= fine");
// Match revolve.c initialization behavior.
if (check_ == -1 && capo_ < fine_)
{
turn_ = 0;
ch_[0] = capo_ - 1;
}
}
int Snaps() const { return snaps_; }
int Check() const { return check_; }
int Capo() const { return capo_; }
int Fine() const { return fine_; }
const std::vector<int>& CheckpointTimes() const { return ch_; }
RevolveAction Next()
{
MFEM_VERIFY(!(check_ < -1), "REVOLVE: check < -1");
MFEM_VERIFY(!(capo_ > fine_), "REVOLVE: capo > fine");
if ((check_ == -1) && (capo_ < fine_))
{
turn_ = 0;
ch_[0] = capo_ - 1;
}
const int diff = fine_ - capo_;
switch (diff)
{
case 0:
{
// Terminate or restore to next checkpoint on the stack.
if (check_ == -1 || capo_ == ch_[0])
{
check_ -= 1; // mirror revolve.c behavior
return RevolveAction::terminate;
}
else
{
capo_ = ch_[check_];
oldfine_ = fine_;
return RevolveAction::restore;
}
}
case 1:
{
// One adjoint step available.
fine_ -= 1;
if (check_ >= 0 && ch_[check_] == capo_) { check_ -= 1; }
if (turn_ == 0)
{
turn_ = 1;
oldfine_ = fine_;
return RevolveAction::firsturn;
}
else
{
oldfine_ = fine_;
return RevolveAction::youturn;
}
}
default:
{
// diff > 1
if (check_ == -1 || ch_[check_] != capo_)
{
// Take a new checkpoint at current capo.
check_ += 1;
MFEM_VERIFY(check_ + 1 <= snaps_, "REVOLVE: exceeded snaps");
ch_[check_] = capo_;
oldfine_ = fine_;
return RevolveAction::takeshot;
}
else
{
// Advance capo forward within (capo, fine).
// This follows the binomial logic in revolve.c.
MFEM_VERIFY(!((oldfine_ < fine_) && (snaps_ == check_ + 1)),
"REVOLVE: fine increased unexpectedly with full stack");
const int oldcapo = capo_;
const int ds = snaps_ - check_;
MFEM_VERIFY(ds >= 1, "REVOLVE: ds < 1");
int reps = 0;
long long range = 1;
while (range < (fine_ - capo_))
{
reps += 1;
// range = range*(reps+ds)/reps (integer arithmetic)
range = range * (reps + ds) / reps;
}
MFEM_VERIFY(reps >= 1, "REVOLVE: reps < 1");
// Binomial helper values (integer)
const long long bino1 = range * reps / (ds + reps);
const long long bino2 = (ds > 1) ? (bino1 * ds / (ds + reps - 1)) : 1;
const long long bino3 =
(ds == 1) ? 0 :
(ds > 2) ? (bino2 * (ds - 1) / (ds + reps - 2)) : 1;
const long long bino4 = bino2 * (reps - 1) / ds;
const long long bino5 =
(ds < 3) ? 0 :
(ds > 3) ? (bino3 * (ds - 2) / reps) : 1;
// Kowarz "new version": keep l^ as small as possible
const long long bino6 = bino1 * ds / reps;
const long long gap = fine_ - capo_;
if (gap <= bino1 + bino3)
{
capo_ += int(bino4);
}
else if (gap < bino1 + bino2)
{
capo_ = fine_ - int(bino2 + bino3);
}
else if (gap <= bino1 + bino2 + bino5)
{
capo_ += int(bino1 - bino3);
}
else
{
capo_ = fine_ - int(bino6);
}
if (capo_ == oldcapo) { capo_ = oldcapo + 1; }
oldfine_ = fine_;
return RevolveAction::advance;
}
}
}
}
};
// -------------------------------
// Fixed-step REVOLVE checkpointing
// -------------------------------
template <typename StorageT>
class FixedStepRevolveCheckpointing
{
public:
struct Shot
{
int time = 0; // state index
int slot = 0; // checkpoint slot index
};
private:
int num_steps_ = 0; // total number of primal steps (0..num_steps)
int num_checkpoints_ = 0; // REVOLVE "snaps"
size_t snapshot_bytes_ = 0;
StorageT *storage_ = nullptr;
std::vector<Shot> forward_shots_;
int forward_shot_cursor_ = 0;
// Controller state at the beginning of the reverse sweep (pre-firsturn).
RevolveController ctrl_init_;
RevolveController ctrl_;
// Two scratch buffers for (de)serialization.
std::vector<uint8_t> io_buf_;
std::vector<uint8_t> prefinal_buf_;
bool prefinal_valid_ = false;
// Reverse sweep bookkeeping.
bool reverse_started_ = false;
int u_work_time_ = -1;
public:
FixedStepRevolveCheckpointing(int num_steps,
int num_checkpoints,
size_t snapshot_bytes,
StorageT &storage)
: num_steps_(num_steps),
num_checkpoints_(num_checkpoints),
snapshot_bytes_(snapshot_bytes),
storage_(&storage),
io_buf_(snapshot_bytes, 0),
prefinal_buf_(snapshot_bytes, 0)
{
MFEM_VERIFY(num_steps_ >= 0, "num_steps must be >= 0");
MFEM_VERIFY(num_checkpoints_ > 0, "num_checkpoints must be > 0");
MFEM_VERIFY(snapshot_bytes_ > 0, "snapshot_bytes must be > 0");
MFEM_VERIFY(storage_->MaxSlots() == num_checkpoints_,
"Storage MaxSlots() must match num_checkpoints");
MFEM_VERIFY(storage_->SlotBytes() == snapshot_bytes_,
"Storage SlotBytes() must match snapshot_bytes");
BuildForwardPlanAndInitialControllerState();
Reset();
}
void Reset()
{
forward_shot_cursor_ = 0;
prefinal_valid_ = false;
reverse_started_ = false;
u_work_time_ = -1;
ctrl_ = ctrl_init_;
}
int NumSteps() const { return num_steps_; }
int NumCheckpoints() const { return num_checkpoints_; }
size_t SnapshotBytes() const { return snapshot_bytes_; }
// ForwardStep: called for i=0..num_steps-1
template <typename State, typename PrimalStep, typename MakeSnapshot>
void ForwardStep(int i,
State &u,
PrimalStep &&primal_step,
MakeSnapshot &&make_snapshot)
{
MFEM_VERIFY(0 <= i && i < num_steps_, "ForwardStep: i out of range");
// Take any planned shots at time i (before advancing).
while (forward_shot_cursor_ < (int)forward_shots_.size() &&
forward_shots_[forward_shot_cursor_].time == i)
{
const int slot = forward_shots_[forward_shot_cursor_].slot;
make_snapshot(u, io_buf_.data(), snapshot_bytes_);
storage_->Save(slot, io_buf_.data(), snapshot_bytes_);
forward_shot_cursor_++;
}
// Cache u_{num_steps-1} so reverse can start there (REVOLVE expects capo=num_steps-1).
if (i == num_steps_ - 1)
{
make_snapshot(u, prefinal_buf_.data(), snapshot_bytes_);
prefinal_valid_ = true;
}
// Advance one step.
primal_step(i, u);
}
// BackwardStep: called for i=num_steps-1..0
template <typename State, typename AdjointState,
typename PrimalStep, typename AdjointStep,
typename MakeSnapshot, typename RestoreSnapshot>
void BackwardStep(int i,
AdjointState &lambda,
State &u_work,
PrimalStep &&primal_step,
AdjointStep &&adjoint_step,
MakeSnapshot &&make_snapshot,
RestoreSnapshot &&restore_snapshot)
{
MFEM_VERIFY(0 <= i && i < num_steps_, "BackwardStep: i out of range");
if (!reverse_started_)
{
MFEM_VERIFY(prefinal_valid_ || num_steps_ == 0,
"Reverse started but prefinal state was not captured. "
"Did you run the forward loop through i=num_steps-1?");
ctrl_ = ctrl_init_;
reverse_started_ = true;
if (num_steps_ > 0)
{
// Restore u_{num_steps-1} into u_work.
restore_snapshot(u_work, prefinal_buf_.data(), snapshot_bytes_);
u_work_time_ = num_steps_ - 1;
}
}
// Execute controller actions until we perform exactly one adjoint step.
for (;;)
{
RevolveAction act = ctrl_.Next();
switch (act)
{
case RevolveAction::takeshot:
{
const int slot = ctrl_.Check();
MFEM_VERIFY(u_work_time_ == ctrl_.Capo(),
"takeshot: u_work_time must equal capo");
make_snapshot(u_work, io_buf_.data(), snapshot_bytes_);
storage_->Save(slot, io_buf_.data(), snapshot_bytes_);
break;
}
case RevolveAction::restore:
{
const int slot = ctrl_.Check();
storage_->Load(slot, io_buf_.data(), snapshot_bytes_);
restore_snapshot(u_work, io_buf_.data(), snapshot_bytes_);
u_work_time_ = ctrl_.Capo();
break;
}
case RevolveAction::advance:
{
const int target = ctrl_.Capo();
MFEM_VERIFY(u_work_time_ >= 0, "advance: u_work_time not initialized");
MFEM_VERIFY(target >= u_work_time_, "advance: target < current time");
for (int t = u_work_time_; t < target; ++t)
{
primal_step(t, u_work);
}
u_work_time_ = target;
break;
}
case RevolveAction::firsturn:
case RevolveAction::youturn:
{
// After firsturn/youturn, ctrl_.Fine() has been decremented and equals ctrl_.Capo().
const int step = ctrl_.Fine();
MFEM_VERIFY(step == ctrl_.Capo(), "youturn: fine != capo");
MFEM_VERIFY(step == u_work_time_, "youturn: u_work_time != step");
MFEM_VERIFY(step == i, "BackwardStep called with i that doesn't match REVOLVE schedule");
// One adjoint step.
adjoint_step(step, u_work, lambda);
return;
}
case RevolveAction::terminate:
{
MFEM_ABORT("REVOLVE terminated early: BackwardStep called after completion?");
break;
}
}
}
}
private:
void BuildForwardPlanAndInitialControllerState()
{
forward_shots_.clear();
RevolveController sim(num_checkpoints_, /*capo=*/0, /*fine=*/num_steps_);
// Forward-plan phase: only TAKESHOT/ADVANCE should occur while fine-capo>1.
while (sim.Fine() - sim.Capo() > 1)
{
RevolveAction a = sim.Next();
if (a == RevolveAction::takeshot)
{
forward_shots_.push_back({sim.Capo(), sim.Check()});
}
else if (a == RevolveAction::advance)
{
// nothing to record; capo moved forward inside sim
}
else
{
MFEM_ABORT("Unexpected REVOLVE action during forward planning phase");
}
}
// At this point, sim is in the pre-firsturn state (capo = num_steps-1, fine = num_steps)
// for num_steps>=1. For num_steps<=1, fine-capo<=1 from the start.
ctrl_init_ = sim;
}
};
#endif //MFEM_REVOLVE_CHECKPOINTING_HPP
+357
View File
@@ -0,0 +1,357 @@
#ifndef MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
#define MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
#include "mfem.hpp"
#include <cstdint>
#include <cstdio> // std::remove
#include <fstream>
#include <iomanip>
#include <limits>
#include <sstream>
#include <string>
#include <vector>
#if __cplusplus >= 201703L
#include <filesystem>
#include <system_error>
#else
#if defined(_WIN32)
#include <direct.h> // _mkdir
#else
#include <sys/stat.h> // mkdir
#include <sys/types.h>
#include <errno.h>
#endif
#endif
namespace mfem
{
/**
* @brief Separate-file checkpoint storage for REVOLVE: one file per checkpoint slot.
*
* This storage is "slot indexed":
* slot = 0..max_slots-1
*
* File naming:
* <dir>/<prefix><slot as zero-padded integer><ext>
*
* Example:
* dir="ckpt", prefix="rev_", ext=".bin", max_slots=8
* -> ckpt/rev_00.bin ... ckpt/rev_07.bin
*
* Intended REVOLVE interface:
* int MaxSlots() const;
* size_t SlotBytes() const;
* void Save(int slot, const uint8_t *src, size_t bytes);
* void Load(int slot, uint8_t *dst, size_t bytes) const;
*
* Notes:
* - Slot files are pre-created and resized on construction if truncate_files=true.
* - Save() overwrites the full slot file content from offset 0.
* - Load() reads exactly SlotBytes() from offset 0.
* - If keep_files=false, destructor deletes the slot files.
* - If keep_open=true, keeps N files open (faster, but uses file descriptors).
*/
class RevolveSeparateFileCheckpointStorage
{
public:
RevolveSeparateFileCheckpointStorage(const std::string &directory,
const std::string &prefix,
const std::string &ext,
int max_slots,
std::size_t slot_bytes,
bool create_dir = true,
bool truncate_files = true,
bool keep_files = true,
bool keep_open = false,
bool flush_on_save = false)
: dir_(directory),
prefix_(prefix),
ext_(ext),
max_slots_(max_slots),
slot_bytes_(slot_bytes),
keep_files_(keep_files),
keep_open_(keep_open),
flush_on_save_(flush_on_save)
{
MFEM_VERIFY(max_slots_ > 0, "RevolveSeparateFileCheckpointStorage: max_slots must be > 0.");
MFEM_VERIFY(slot_bytes_ > 0, "RevolveSeparateFileCheckpointStorage: slot_bytes must be > 0.");
MFEM_VERIFY(!prefix_.empty(), "RevolveSeparateFileCheckpointStorage: prefix must not be empty.");
if (create_dir) { EnsureDirectory_(dir_); }
// Precompute slot paths.
slot_paths_.resize((std::size_t)max_slots_);
for (int s = 0; s < max_slots_; ++s)
{
slot_paths_[(std::size_t)s] = MakeSlotPath_(s);
}
if (truncate_files)
{
PrecreateAll_();
}
else
{
// Optional light sanity check: try opening one file. (User may want to reuse existing.)
// We'll rely on Save/Load verification otherwise.
}
if (keep_open_)
{
OpenAll_();
}
}
~RevolveSeparateFileCheckpointStorage()
{
CloseAll_();
if (!keep_files_)
{
RemoveAllFiles_();
}
}
int MaxSlots() const { return max_slots_; }
std::size_t SlotBytes() const { return slot_bytes_; }
/// Return the full path for a given slot (useful for debugging).
const std::string &SlotPath(int slot) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "SlotPath: slot out of range.");
return slot_paths_[(std::size_t)slot];
}
/**
* @brief Save a checkpoint image into slot file.
*
* Requirements:
* - slot in [0, MaxSlots())
* - bytes == SlotBytes()
* - src != nullptr
*/
void Save(int slot, const std::uint8_t *src, std::size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range.");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch.");
MFEM_VERIFY(src != nullptr, "Save: src is null.");
if (keep_open_)
{
std::fstream &f = files_[(std::size_t)slot];
MFEM_VERIFY(f.is_open(), "Save: file not open (keep_open).");
f.clear();
f.seekp(0, std::ios::beg);
MFEM_VERIFY(f.good(), "Save: seekp failed (keep_open).");
f.write(reinterpret_cast<const char*>(src), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Save: write failed (keep_open).");
if (flush_on_save_) { f.flush(); }
return;
}
// Open on demand
std::fstream f(SlotPath(slot).c_str(),
std::ios::binary | std::ios::in | std::ios::out);
if (!f.is_open())
{
// If missing, create it sized correctly, then reopen.
PrecreateOne_(slot);
f.open(SlotPath(slot).c_str(), std::ios::binary | std::ios::in | std::ios::out);
}
MFEM_VERIFY(f.is_open(), "Save: failed to open slot file.");
f.seekp(0, std::ios::beg);
MFEM_VERIFY(f.good(), "Save: seekp failed.");
f.write(reinterpret_cast<const char*>(src), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Save: write failed.");
if (flush_on_save_) { f.flush(); }
}
/**
* @brief Load a checkpoint image from slot file.
*
* Requirements:
* - slot in [0, MaxSlots())
* - bytes == SlotBytes()
* - dst != nullptr
*/
void Load(int slot, std::uint8_t *dst, std::size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range.");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch.");
MFEM_VERIFY(dst != nullptr, "Load: dst is null.");
if (keep_open_)
{
std::fstream &f = files_[(std::size_t)slot];
MFEM_VERIFY(f.is_open(), "Load: file not open (keep_open).");
f.clear();
f.seekg(0, std::ios::beg);
MFEM_VERIFY(f.good(), "Load: seekg failed (keep_open).");
f.read(reinterpret_cast<char*>(dst), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Load: read failed (keep_open).");
return;
}
std::ifstream f(SlotPath(slot).c_str(), std::ios::binary);
MFEM_VERIFY(f.is_open(), "Load: failed to open slot file.");
f.read(reinterpret_cast<char*>(dst), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Load: read failed.");
}
private:
std::string dir_;
std::string prefix_;
std::string ext_;
int max_slots_ = 0;
std::size_t slot_bytes_ = 0;
bool keep_files_ = true;
bool keep_open_ = false;
bool flush_on_save_ = false;
std::vector<std::string> slot_paths_;
mutable std::vector<std::fstream> files_; // only used if keep_open_==true
static std::string JoinPath_(const std::string &dir, const std::string &file)
{
if (dir.empty()) { return file; }
const char last = dir.back();
if (last == '/' || last == '\\') { return dir + file; }
return dir + "/" + file;
}
int SlotDigits_() const
{
int x = max_slots_ - 1;
int d = 1;
while (x >= 10) { x /= 10; ++d; }
return d;
}
std::string MakeSlotFilename_(int slot) const
{
std::ostringstream os;
os << prefix_
<< std::setw(SlotDigits_()) << std::setfill('0') << slot
<< ext_;
return os.str();
}
std::string MakeSlotPath_(int slot) const
{
return JoinPath_(dir_, MakeSlotFilename_(slot));
}
void PrecreateOne_(int slot) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "PrecreateOne: slot out of range.");
// Create/truncate and set file size to slot_bytes_ by writing last byte.
std::ofstream ofs(SlotPath(slot).c_str(), std::ios::binary | std::ios::trunc);
MFEM_VERIFY(ofs.is_open(), "PrecreateOne: failed to create slot file.");
const std::uint64_t sb = (std::uint64_t)slot_bytes_;
MFEM_VERIFY(sb <= (std::uint64_t)std::numeric_limits<std::streamoff>::max(),
"PrecreateOne: slot_bytes too large for streamoff.");
if (sb > 0)
{
ofs.seekp((std::streamoff)(sb - 1), std::ios::beg);
MFEM_VERIFY(ofs.good(), "PrecreateOne: seekp failed.");
const char zero = 0;
ofs.write(&zero, 1);
MFEM_VERIFY(ofs.good(), "PrecreateOne: size write failed.");
}
}
void PrecreateAll_() const
{
for (int slot = 0; slot < max_slots_; ++slot)
{
PrecreateOne_(slot);
}
}
void OpenAll_()
{
files_.resize((std::size_t)max_slots_);
for (int slot = 0; slot < max_slots_; ++slot)
{
std::fstream &f = files_[(std::size_t)slot];
f.open(SlotPath(slot).c_str(), std::ios::binary | std::ios::in | std::ios::out);
if (!f.is_open())
{
PrecreateOne_(slot);
f.open(SlotPath(slot).c_str(), std::ios::binary | std::ios::in | std::ios::out);
}
MFEM_VERIFY(f.is_open(), "OpenAll: failed to open slot file.");
}
}
void CloseAll_()
{
if (!files_.empty())
{
for (auto &f : files_) { if (f.is_open()) { f.close(); } }
files_.clear();
}
}
void RemoveAllFiles_() const
{
for (int slot = 0; slot < max_slots_; ++slot)
{
// Ignore remove errors (e.g., already removed), but you can tighten if desired.
std::remove(SlotPath(slot).c_str());
}
}
static void EnsureDirectory_(const std::string &dir)
{
if (dir.empty()) { return; }
#if __cplusplus >= 201703L
namespace fs = std::filesystem;
std::error_code ec;
if (!fs::exists(dir, ec))
{
fs::create_directories(dir, ec);
}
MFEM_VERIFY(!ec, "EnsureDirectory: failed to create directory: " + dir);
#else
#if defined(_WIN32)
const int rc = _mkdir(dir.c_str());
if (rc != 0)
{
// If directory already exists, _mkdir fails. We accept that.
// There's no reliable portable "exists" check in pre-C++17 without more code.
}
#else
const int rc = mkdir(dir.c_str(), 0755);
if (rc != 0 && errno != EEXIST)
{
MFEM_ABORT("EnsureDirectory: failed to create directory: " + dir);
}
#endif
#endif
}
};
} // namespace mfem
#endif // MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
@@ -0,0 +1,273 @@
#ifndef MFEM_SEGMENT_CHECKPOINT_STORAGE_HPP
#define MFEM_SEGMENT_CHECKPOINT_STORAGE_HPP
#include "mfem.hpp"
#include "file_checkpoint_storage.hpp" // reuses DefaultCheckpointBinaryIO
#include <cstdint>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <set>
#if __has_include(<filesystem>)
#include <filesystem>
namespace mfem_fs = std::filesystem;
#define MFEM_HAVE_FILESYSTEM 1
#else
#define MFEM_HAVE_FILESYSTEM 0
#endif
namespace mfem
{
/**
* @brief Segment/range file storage: one file per handle-id range + in-file offsets.
*
* Segment file = <dir>/<prefix><segment_id><ext>
* Where segment_id = handle / records_per_file.
*
* Pros:
* - Much fewer files than file-per-snapshot
* - No per-snapshot file create/delete
*
* Cons:
* - Append-only: Erase() does not reclaim file space (records remain)
* - Index is in-memory (not restartable across processes unless you persist it)
*/
template <typename Snapshot,
typename SnapshotIO = DefaultCheckpointBinaryIO<Snapshot> >
class SegmentedFileCheckpointStorage
{
public:
using Handle = std::int64_t;
SegmentedFileCheckpointStorage(const std::string &directory,
std::int64_t records_per_file = 4096,
const std::string &prefix = "seg_",
const std::string &extension = ".bin",
bool create_dir = true,
bool keep_files = false)
: dir_(directory),
prefix_(prefix),
ext_(extension),
keep_files_(keep_files),
records_per_file_(records_per_file)
{
MFEM_VERIFY(records_per_file_ > 0, "SegmentedFileCheckpointStorage: records_per_file must be > 0.");
MFEM_VERIFY(!dir_.empty(), "SegmentedFileCheckpointStorage: empty directory.");
if (create_dir)
{
#if MFEM_HAVE_FILESYSTEM
std::error_code ec;
mfem_fs::create_directories(mfem_fs::path(dir_), ec);
MFEM_VERIFY(!ec, "SegmentedFileCheckpointStorage: failed to create directory.");
#else
MFEM_ABORT("SegmentedFileCheckpointStorage: create_dir=true requires <filesystem> support.");
#endif
}
}
Handle InvalidHandle() const { return (Handle)-1; }
bool IsValid(const Handle &h) const { return h >= 0; }
Handle Store(Snapshot &&snap)
{
const Handle id = AllocateId_();
EnsureMetaSize_(id);
const std::int64_t seg = SegmentId_(id);
const std::string path = SegmentPath_(seg);
const std::uint64_t offset = AppendRecord_(path, snap);
const std::uint64_t bytes = last_payload_bytes_;
meta_[id].valid = true;
meta_[id].seg_id = seg;
meta_[id].offset = offset;
meta_[id].bytes = bytes;
touched_segments_.insert(seg);
return id;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "SegmentedFileCheckpointStorage: Read invalid handle.");
MFEM_VERIFY(h < (Handle)meta_.size(), "SegmentedFileCheckpointStorage: Read out-of-range handle.");
MFEM_VERIFY(meta_[h].valid, "SegmentedFileCheckpointStorage: Read on erased handle.");
const std::string path = SegmentPath_(meta_[h].seg_id);
std::ifstream is(path, std::ios::binary);
MFEM_VERIFY(is.is_open(), "SegmentedFileCheckpointStorage: open-for-read failed.");
// Seek to record start
is.seekg((std::streamoff)meta_[h].offset, std::ios::beg);
MFEM_VERIFY(is.good(), "SegmentedFileCheckpointStorage: seekg failed.");
std::uint64_t payload_bytes = 0;
is.read(reinterpret_cast<char*>(&payload_bytes), sizeof(payload_bytes));
MFEM_VERIFY(is.good(), "SegmentedFileCheckpointStorage: read header failed.");
MFEM_VERIFY(payload_bytes == meta_[h].bytes, "SegmentedFileCheckpointStorage: payload size mismatch.");
Snapshot snap = SnapshotIO::Read(is);
MFEM_VERIFY(is.good(), "SegmentedFileCheckpointStorage: payload read failed.");
f(snap);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < (Handle)meta_.size(), "SegmentedFileCheckpointStorage: Erase out-of-range handle.");
meta_[h].valid = false;
if (!keep_files_)
{
free_.push_back(h); // allow handle reuse
}
h = InvalidHandle();
}
/**
* @brief Optional cleanup helper (NOT part of the required storage interface).
*
* Deletes all segment files touched by this storage object.
*/
void PurgeAllFiles()
{
#if MFEM_HAVE_FILESYSTEM
for (auto seg : touched_segments_)
{
const std::string path = SegmentPath_(seg);
std::error_code ec;
mfem_fs::remove(mfem_fs::path(path), ec);
// Best-effort cleanup:
MFEM_VERIFY(!ec, "SegmentedFileCheckpointStorage: failed to remove segment file.");
}
touched_segments_.clear();
#else
MFEM_ABORT("SegmentedFileCheckpointStorage: PurgeAllFiles requires <filesystem> support.");
#endif
}
private:
struct Meta
{
bool valid = false;
std::int64_t seg_id = 0;
std::uint64_t offset = 0;
std::uint64_t bytes = 0;
};
std::string dir_, prefix_, ext_;
bool keep_files_ = false;
std::int64_t records_per_file_ = 4096;
mutable Handle next_id_ = 0;
mutable std::vector<Handle> free_;
std::vector<Meta> meta_;
mutable std::uint64_t last_payload_bytes_ = 0;
std::set<std::int64_t> touched_segments_;
void EnsureMetaSize_(Handle id)
{
if ((std::size_t)id >= meta_.size())
{
meta_.resize((std::size_t)id + 1);
}
}
Handle AllocateId_()
{
if (!keep_files_ && !free_.empty())
{
const Handle id = free_.back();
free_.pop_back();
return id;
}
return next_id_++;
}
std::int64_t SegmentId_(Handle id) const
{
return (std::int64_t)(id / records_per_file_);
}
std::string SegmentPath_(std::int64_t seg) const
{
std::ostringstream oss;
oss << prefix_ << std::setw(8) << std::setfill('0') << seg << ext_;
#if MFEM_HAVE_FILESYSTEM
mfem_fs::path p = mfem_fs::path(dir_) / mfem_fs::path(oss.str());
return p.string();
#else
return dir_ + "/" + oss.str();
#endif
}
static void EnsureFileExists_(const std::string &path)
{
// Try open for read/write; if missing, create.
std::fstream fs(path, std::ios::binary | std::ios::in | std::ios::out);
if (!fs.is_open())
{
std::ofstream create(path, std::ios::binary | std::ios::out);
MFEM_VERIFY(create.is_open(), "SegmentedFileCheckpointStorage: file create failed.");
}
}
// Append framed record to file. Returns offset of record start. Updates last_payload_bytes_.
std::uint64_t AppendRecord_(const std::string &path, const Snapshot &snap)
{
EnsureFileExists_(path);
std::fstream fs(path, std::ios::binary | std::ios::in | std::ios::out);
MFEM_VERIFY(fs.is_open(), "SegmentedFileCheckpointStorage: open-for-append failed.");
fs.seekp(0, std::ios::end);
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: seekp(end) failed.");
const std::streamoff begin = (std::streamoff)fs.tellp();
MFEM_VERIFY(begin >= 0, "SegmentedFileCheckpointStorage: tellp failed.");
// placeholder for payload bytes
std::uint64_t payload_bytes = 0;
fs.write(reinterpret_cast<const char*>(&payload_bytes), sizeof(payload_bytes));
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: write header failed.");
// payload
SnapshotIO::Write(fs, snap);
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: write payload failed.");
const std::streamoff end = (std::streamoff)fs.tellp();
MFEM_VERIFY(end >= begin, "SegmentedFileCheckpointStorage: tellp end failed.");
payload_bytes = (std::uint64_t)(end - begin - (std::streamoff)sizeof(std::uint64_t));
last_payload_bytes_ = payload_bytes;
// backpatch payload size
fs.seekp(begin, std::ios::beg);
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: seekp(begin) failed.");
fs.write(reinterpret_cast<const char*>(&payload_bytes), sizeof(payload_bytes));
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: backpatch failed.");
return (std::uint64_t)begin;
}
};
} // namespace mfem
#endif // MFEM_SEGMENT_CHECKPOINT_STORAGE_HPP
+701
View File
@@ -0,0 +1,701 @@
#include "mfem.hpp"
#include "dynamic_checkpointing.hpp"
#include "fixed_slot_checkpoint_storage.hpp"
#include "file_checkpoint_storage.hpp"
#include <cmath>
#include <iomanip>
#include <string>
using namespace mfem;
/************************************************************************
* The forward state consists of vector and several additional values.
* The goal is to demontrate how different storages can be used together
* with DynamicCheckpointing in order to avoid unnecessary memory
* allocations, data copies, and deallocations.
* *********************************************************************/
struct State
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
mfem::Vector v;
};
// ---------------------------
// Snapshot type used by storage:
// a lightweight view (non-owning).
// ---------------------------
struct StateSnapshotView
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
// Points to n*sizeof(real_t) bytes.
// - during Store(): points to State::v host data
// - during Read(): points into storage's internal scratch buffer
const unsigned char *v_bytes = nullptr;
};
// ---------------------------
// Packer for fixed-slot storage
// Layout in slot bytes:
// [ time | obj | v[0..n-1] ]
// ---------------------------
class StateSnapshotViewPacker
{
public:
explicit StateSnapshotViewPacker(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "StateSnapshotViewPacker: n must be > 0.");
}
std::size_t SlotBytes() const
{
return (std::size_t)(2 + n_) * sizeof(mfem::real_t);
}
void Pack(const StateSnapshotView &s, void *dst) const
{
MFEM_VERIFY(dst != nullptr, "Pack: dst is null.");
MFEM_VERIFY(s.v_bytes != nullptr, "Pack: v_bytes is null.");
unsigned char *b = static_cast<unsigned char*>(dst);
std::memcpy(b + 0*sizeof(mfem::real_t), &s.time, sizeof(mfem::real_t));
std::memcpy(b + 1*sizeof(mfem::real_t), &s.obj, sizeof(mfem::real_t));
std::memcpy(b + 2*sizeof(mfem::real_t),
s.v_bytes,
(std::size_t)n_ * sizeof(mfem::real_t));
}
// IMPORTANT:
// Unpack produces a view into the provided src buffer.
// The resulting pointer is only valid as long as src remains unchanged.
void Unpack(const void *src, StateSnapshotView &out) const
{
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
const unsigned char *b = static_cast<const unsigned char*>(src);
std::memcpy(&out.time, b + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(&out.obj, b + 1*sizeof(mfem::real_t), sizeof(mfem::real_t));
out.v_bytes = b + 2*sizeof(mfem::real_t);
}
int N() const { return n_; }
private:
int n_ = 0;
};
/*****************************************************************************
* Provides a recipe for runing dynamic checkpointing with memory storage
* alocated as one single big block.
* **************************************************************************/
void run_fixed_slot_memory_storage( mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
bool print=true)
{
StateSnapshotViewPacker packer(n);
using Storage = mfem::FixedSlotMemoryCheckpointStorage<StateSnapshotView,
StateSnapshotViewPacker>;
Storage storage(s, packer);
// Snapshot type is StateSnapshotView
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshotView, Storage>;
Checkpointing ckpt(s, storage);
// Returns view of the State and avoids data transfer
auto make_snapshot = [&](const State &u) -> StateSnapshotView
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
const mfem::real_t *vh = u.v.HostRead(); // host pointer
StateSnapshotView snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v_bytes = reinterpret_cast<const unsigned char*>(vh);
return snap;
};
//Transfers data from the snaphot view to the State u_out.
auto restore_snapshot = [&](const StateSnapshotView &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
mfem::real_t *vh = u_out.v.HostWrite();
std::memcpy(vh,
snap.v_bytes,
(std::size_t)n * sizeof(mfem::real_t));
};
using Step = Checkpointing::Step;
State u;
u.v.SetSize(n); u.v=0.0;
auto primal_step = [&](State &u_inout, Step i)
{
// advance u_inout.v, u_inout.time, u_inout.obj
if(print)
std::cout<<"Forward step: "<<i<<" time="<<u_inout.time<<" obj="<<u_inout.obj<<std::endl;
u_inout.time=i*0.1;
u_inout.obj=i*0.2;
};
struct AdjointState { /* ... */ };
AdjointState q;
auto adjoint_step = [&](AdjointState &q_ip1_inout, const State &u_i, Step i)
{
// use u_i.time/u_i.obj/u_i.v to update adjoint
if(print)
std::cout<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
};
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while(t<Tfinal)
{
// Store snapshot of u_i when manager decides; then primal_step u_i -> u_{i+1}
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
const Step m=i;
// Backward sweep
State u_work;
u_work.v.SetSize(n); // allocate once
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
/****************************************************************************
* Provides a recipe for runing dynamic checkpointing with file storage
* alocated as one single big file.
****************************************************************************/
/****************************************************************************
* The dynamic algorithm conceptually maintains s + 1 checkpoints, but the
* last one is a placeholder checkpoint that stores no solution and takes
* little memory; only s are real stored solutions.
* So FixedSlotFileCheckpointStorage needs only s slots.
* File behavior for FixedSlotFileCheckpointStorage:
- Writes a small header + s * slot_bytes payload region,
- Erase(handle) just returns the slot to the free list
(file contents remain, will be overwritten later),
no filesystem metadata churn.
* Device memory: Runs on GPU -packing to a host file requires a host pointer.
Using:
u.v.HostRead() for packing,
u_out.v.HostWrite() for restore,
keeps memmory access correct.
* Snapshot pointer lifetime: In the read path, StateSnapshotViewPacker::Unpack
sets snap.v_bytes to point into the storages internal read buffer. That
pointer is only valid until the storage performs another Read/Store
(and in general, one should treat it as valid only during restore_snapshot).
*****************************************************************************/
void run_fixed_slot_file_storage(mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
const std::string &file_path,
bool print=true)
{
// s = number of REAL checkpoints (placeholder doesn't store a snapshot)
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
StateSnapshotViewPacker packer(n);
// Fixed-slot FILE storage:
// - single file (file_path)
// - max_slots = s (real checkpoints)
// - slot_bytes = packer.SlotBytes()
// - truncate=true to start fresh
// - flush_on_store=false for performance (set true if you want extra safety)
using Storage = mfem::FixedSlotFileCheckpointStorage<StateSnapshotView, StateSnapshotViewPacker>;
Storage storage(file_path,
/*max_slots=*/s,
/*packer=*/packer,
/*truncate=*/true,
/*flush_on_store=*/false);
// Dynamic checkpointing manager using this storage.
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshotView, Storage>;
Checkpointing ckpt(s, storage);
// ---------------------------
// Callbacks
// ---------------------------
// Convert State -> SnapshotView (no allocation).
auto make_snapshot = [&](const State &u) -> StateSnapshotView
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
// Ensure host pointer valid even when MFEM is using device memory.
const mfem::real_t *vh = u.v.HostRead();
StateSnapshotView snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v_bytes = reinterpret_cast<const unsigned char*>(vh);
return snap;
};
// Convert SnapshotView -> State (must COPY out of v_bytes).
auto restore_snapshot = [&](const StateSnapshotView &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
mfem::real_t *vh = u_out.v.HostWrite();
std::memcpy(vh,
snap.v_bytes,
(std::size_t)n * sizeof(mfem::real_t));
};
// Example primal/adjoint step signatures (replace with your own):
using Step = Checkpointing::Step;
auto primal_step = [&](State &u_inout, Step i)
{
if(print)
std::cout<<"Forward step: "<<i<<" time="<<u_inout.time<<" obj="<<u_inout.obj<<std::endl;
u_inout.time=i*0.1;
u_inout.obj=i*0.2;
// update u_inout.time, u_inout.obj, u_inout.v
};
struct AdjointState
{
// your adjoint variables, e.g., mfem::Vector lambda;
};
auto adjoint_step = [&](AdjointState &q_ip1_inout, const State &u_i, Step i)
{
// update adjoint using u_i
if(print)
std::cout<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
};
// ---------------------------
// Forward/backward skeleton
// ---------------------------
State u;
u.v.SetSize(n);
u.time = 0.0;
u.obj = 0.0;
u.v = 0.0;
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while(t<Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
// Backward sweep
AdjointState q;
const Step m=i;
State u_work;
u_work.v.SetSize(n);
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
/******************************************************************************
The following classes implement the IO and the data necessery for dynamic
checkpointing with mfem::FileCheckpointStorage (one file per stored snapshot).
With FileCheckpointStorage, the Snapshot must own the data which will be
written, i.e. it cannot be a view containing pointers into State::v
as those pointers would be meaningless when read back.
*******************************************************************************/
// The StateSnapshot is just a copy of the State
using StateSnapshot=State;
struct StateSnapshotBinaryIO
{
static void Write(std::ostream &os, const StateSnapshot &s)
{
os.write(reinterpret_cast<const char*>(&s.time), sizeof(mfem::real_t));
os.write(reinterpret_cast<const char*>(&s.obj), sizeof(mfem::real_t));
MFEM_VERIFY(os.good(), "StateSnapshotBinaryIO: write time/obj failed.");
const std::int64_t n = (std::int64_t)s.v.Size();
os.write(reinterpret_cast<const char*>(&n), sizeof(n));
MFEM_VERIFY(os.good(), "StateSnapshotBinaryIO: write vector size failed.");
if (n > 0)
{
const mfem::real_t *vh = s.v.HostRead(); // ensure host pointer
os.write(reinterpret_cast<const char*>(vh),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(os.good(), "StateSnapshotBinaryIO: write vector payload failed.");
}
}
static StateSnapshot Read(std::istream &is)
{
StateSnapshot s;
is.read(reinterpret_cast<char*>(&s.time), sizeof(mfem::real_t));
is.read(reinterpret_cast<char*>(&s.obj), sizeof(mfem::real_t));
MFEM_VERIFY(is.good(), "StateSnapshotBinaryIO: read time/obj failed.");
std::int64_t n = 0;
is.read(reinterpret_cast<char*>(&n), sizeof(n));
MFEM_VERIFY(is.good(), "StateSnapshotBinaryIO: read vector size failed.");
MFEM_VERIFY(n >= 0, "StateSnapshotBinaryIO: invalid negative vector size.");
s.v.SetSize((int)n);
if (n > 0)
{
mfem::real_t *vh = s.v.HostWrite(); // ensure host pointer
is.read(reinterpret_cast<char*>(vh),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(is.good(), "StateSnapshotBinaryIO: read vector payload failed.");
}
return s;
}
};
/***************************************************************************
Important differences vs FixedSlotFileCheckpointStorage
Metadata overhead
FileCheckpointStorage typically:
*creates a new file for each Store() (plus a temp file rename),
*deletes a file on each Erase() (unless keep_files=true).
Dynamic checkpointing can perform many store/erase operations during
forward and during recomputation in reverse, so this can hammer filesystem
metadata on parallel filesystems.
If thats a concern, prefer:
*fixed-slot single-file storage, or
*segmented range file storage (few files + offsets).
Correctness note about pointers:
This is why we used an owning snapshot (StateSnapshot): storing a view
with v_bytes into file storage would store pointer values, which are
meaningless when read back.
*****************************************************************************/
void run_file_storage(mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
const std::string &directory,
bool print=true)
{
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
// One file per snapshot (create_dir=true). keep_files=false means Erase() removes files.
using Storage = mfem::FileCheckpointStorage<StateSnapshot, StateSnapshotBinaryIO>;
Storage storage(directory, "ckpt_", ".bin", /*create_dir=*/true, /*keep_files=*/false);
// Snapshot type = StateSnapshot (owning)
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshot, Storage>;
Checkpointing ckpt(s, storage);
using Step = Checkpointing::Step;
// ---- Callbacks ----
// Make an owning snapshot from the current State (deep copy of vector payload)
auto make_snapshot = [&](const State &u) -> StateSnapshot
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
StateSnapshot snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v.SetSize(n);
const mfem::real_t *src = u.v.HostRead();
mfem::real_t *dst = snap.v.HostWrite();
std::memcpy(dst, src, (std::size_t)n * sizeof(mfem::real_t));
return snap; // move into storage
};
// Restore from snapshot into an actual State (copy payload)
auto restore_snapshot = [&](const StateSnapshot &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
MFEM_VERIFY(snap.v.Size() == n, "restore_snapshot: snapshot vector size mismatch.");
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
const mfem::real_t *src = snap.v.HostRead();
mfem::real_t *dst = u_out.v.HostWrite();
std::memcpy(dst, src, (std::size_t)n * sizeof(mfem::real_t));
};
// Example primal step signature (replace with your integrator)
auto primal_step = [&](State &u_inout, Step i)
{
// advance u_inout.time, u_inout.obj, u_inout.v
if(print)
std::cout<<"Forward step: "<<i<<" time="<<u_inout.time<<" obj="<<u_inout.obj<<std::endl;
u_inout.time=i*0.1;
u_inout.obj=i*0.2;
};
// Example adjoint state and adjoint step signature (replace with yours)
struct AdjointState
{
// e.g., mfem::Vector lambda;
};
auto adjoint_step = [&](AdjointState &q_ip1_inout, const State &u_i, Step i)
{
// update adjoint using u_i
if(print)
std::cout<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
};
// ---- Forward/backward skeleton ----
State u;
u.v.SetSize(n);
u.time = 0.0;
u.obj = 0.0;
u.v = 0.0;
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while(t<Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
// backward
const Step m=i;
AdjointState q;
State u_work;
u_work.v.SetSize(n);
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
/****************************************************************************
* Using standard in memory storage. Works with variable snaphot sizes.
* The size of the vector (n) is provided in order to compare the run to
* the other implementations.
***************************************************************************/
void run_in_memory_storage(mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
bool print=true)
{
InMemoryCheckpointStorage<StateSnapshot> storage;
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshot,
InMemoryCheckpointStorage<StateSnapshot>>;
Checkpointing ckpt(s, storage);
auto make_snapshot = [&](const State &u) -> StateSnapshot
{
StateSnapshot snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v = u.v; // deep copy (MFEM handles memory)
return snap; // moved into storage by Store()
};
auto restore_snapshot = [&](const StateSnapshot &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
u_out.v = snap.v; // deep copy back into working state
};
using Step = Checkpointing::Step;
// Primal step: u_i -> u_{i+1}
auto primal_step = [&](State &u, Step i)
{
// ... update u.time, u.obj, u.v ...
if(print)
std::cout<<"Forward step: "<<i<<" time="<<u.time<<" obj="<<u.obj<<std::endl;
u.time=i*0.1;
u.obj=i*0.2;
};
// Adjoint step: q_{i+1} -> q_i using u_i
struct AdjointState
{
// e.g. mfem::Vector lambda;
};
auto adjoint_step = [&](AdjointState &q, const State &u_i, Step i)
{
// ... update q using u_i ...
if(print)
std::cout<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
};
State u;
u.v.SetSize(n);
u.v=0.0;
u.time = 0.0;
u.obj = 0.0;
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while(t<Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
// Backward
const Step m=i;
AdjointState q;
State u_work; // used to restore/recompute primal state at step i
u_work.v.SetSize(u.v.Size());
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
int main(int argc, char *argv[])
{
int s = 5/* checkpoint budget (real checkpoints) */;
int n = 30 /* fixed State.v size */;
std::string filepath="dynamic_ckpts.bin"; /*file name for fixed slot single file*/
std::string directory="dyn_ckpts";
mfem::real_t Tfinal=1;
mfem::real_t dt=0.1;
int print=true;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s",
"--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&n, "-n",
"--size", "Size of the state vector.");
args.AddOption(&Tfinal, "-T",
"--tfinal", "Terminate when accumulated time reaches Tfinal.");
args.AddOption(&dt, "-dt", "--dt", "Time step.");
args.AddOption(&filepath, "-file", "--file",
"File name for storing the checkpoints.");
args.AddOption(&directory, "-dir", "--dir",
"Directory for storing the checkpoints.");
args.AddOption(&print, "-print", "--print",
"Turn on/off printing." );
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
double run_time;
mfem::out<<"\nFixedSlotMemoryStorage Start\n";
mfem::tic();
run_fixed_slot_memory_storage(Tfinal, dt, s, n, print);
run_time=mfem::toc();
mfem::out<<"FixedSlotMemoryStorage time= "<<run_time<<std::endl;
mfem::out<<"\nFixedSlotFileStorage Start\n";
mfem::tic();
run_fixed_slot_file_storage(Tfinal,dt, s,n, filepath,print);
run_time=mfem::toc();
mfem::out<<"FixedSlotFileStorage time= "<<run_time<<std::endl;
mfem::out<<"\nFileStorage Start\n";
mfem::tic();
run_file_storage(Tfinal,dt, s,n, directory, print);
run_time=mfem::toc();
mfem::out<<"FileStorage time= "<<run_time<<std::endl;
mfem::out<<"\nInMemoryStorage\n";
mfem::tic();
run_in_memory_storage(Tfinal,dt, s,n, print);
run_time=mfem::toc();
mfem::out<<"InMemoryStorage time= "<<run_time<<std::endl;
mfem::out << "\nDone.\n";
return 0;
}
@@ -0,0 +1,300 @@
#include "mfem.hpp"
#include "dynamic_checkpointing.hpp"
#include "file_checkpoint_storage.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
static double LogisticStep(double u, double alpha, double dt)
{
return u + dt * alpha * u * (1.0 - u);
}
static double LogisticJac(double u, double alpha, double dt)
{
// d/du [u + dt*alpha*u*(1-u)] = 1 + dt*alpha*(1 - 2u)
return 1.0 + dt * alpha * (1.0 - 2.0*u);
}
#if MFEM_HAVE_FILESYSTEM
static void PurgeDir(const std::string &dir)
{
std::error_code ec;
mfem_fs::remove_all(mfem_fs::path(dir), ec);
mfem_fs::create_directories(mfem_fs::path(dir), ec);
MFEM_VERIFY(!ec, "Failed to purge/create directory.");
}
#endif
int main(int argc, char *argv[])
{
// ---------- shared options ----------
int s = 3;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
bool keep_files = false;
bool purge_dirs = true;
// scalar problem params
double alpha = 2.0;
double u0 = 0.2;
double target_s = 0.7;
double eps = 1e-7;
// vector problem params
int n = 16;
double target_v = 0.7;
std::string dir_scalar = "chk_scalar_files";
std::string dir_vector = "chk_vector_files";
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&dt0, "-dt0", "--dt0", "Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega", "Omega for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal", "Stop when accumulated time reaches Tfinal.");
args.AddOption(&keep_files, "-k", "--keep-files", "-nk", "--no-keep-files",
"Keep checkpoint files (debug).");
args.AddOption(&purge_dirs, "-p", "--purge-dirs", "-np", "--no-purge-dirs",
"Purge checkpoint directories at start (recommended).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&u0, "-u0", "--u0", "Scalar initial u0.");
args.AddOption(&target_s, "-ts", "--target-scalar", "Scalar target.");
args.AddOption(&eps, "-eps", "--fd-eps", "FD epsilon (scalar and vector directional).");
args.AddOption(&n, "-n", "--size", "Vector dimension.");
args.AddOption(&target_v, "-tv", "--target-vector", "Vector target value per component.");
args.AddOption(&dir_scalar, "-ds", "--dir-scalar", "Directory for scalar checkpoint files.");
args.AddOption(&dir_vector, "-dv", "--dir-vector", "Directory for vector checkpoint files.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
#if MFEM_HAVE_FILESYSTEM
if (purge_dirs)
{
PurgeDir(dir_scalar);
PurgeDir(dir_vector);
}
#else
MFEM_VERIFY(!purge_dirs, "purge_dirs requires <filesystem> support.");
#endif
using StepS = mfem::DynamicCheckpointing<double, mfem::FileCheckpointStorage<double>>::Step;
auto dt_func = [&](StepS i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
mfem::out << std::setprecision(15);
// ============================================================
// A) Scalar run (double) with FileCheckpointStorage
// ============================================================
{
mfem::FileCheckpointStorage<double> storage(dir_scalar, "ckpt_", ".bin", true, keep_files);
mfem::DynamicCheckpointing<double, mfem::FileCheckpointStorage<double>> ckpt(s, storage);
auto make_snapshot = [](const double &u) { return u; };
auto restore_snapshot = [](const double &snap, double &out) { out = snap; };
auto primal_step = [&](double &u, StepS i)
{
const double dt = dt_func(i);
u = LogisticStep(u, alpha, dt);
};
auto adjoint_step = [&](double &lambda, const double &u_i, StepS i)
{
const double dt = dt_func(i);
const double dF = LogisticJac(u_i, alpha, dt);
lambda *= dF;
};
double u = u0;
double t_phys = 0.0;
StepS i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const StepS m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target_s) * (u_m - target_s);
double lambda = (u_m - target_s); // terminal adjoint
double u_work = 0.0;
for (StepS j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const double dJ_du0_adj = lambda;
// FD check
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
StepS k = 0;
while (tt < Tfinal)
{
uu = LogisticStep(uu, alpha, dt_func(k));
tt += dt_func(k);
++k;
}
const double r = uu - target_s;
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
mfem::out << "\n[FileIO][Scalar]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " dJ/du0 adjoint = " << dJ_du0_adj << "\n";
mfem::out << " dJ/du0 FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_du0_adj - dJ_du0_fd) << "\n";
}
// ============================================================
// B) Vector run (mfem::Vector) with FileCheckpointStorage
// ============================================================
{
mfem::FileCheckpointStorage<mfem::Vector> storage(dir_vector, "ckpt_", ".bin", true, keep_files);
mfem::DynamicCheckpointing<mfem::Vector, mfem::FileCheckpointStorage<mfem::Vector>> ckpt(s, storage);
using StepV = mfem::DynamicCheckpointing<mfem::Vector, mfem::FileCheckpointStorage<mfem::Vector>>::Step;
auto dt_func_v = [&](StepV i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const mfem::Vector &u) { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &out) { out = snap; };
auto primal_step = [&](mfem::Vector &u, StepV i)
{
const double dt = dt_func_v(i);
for (int j = 0; j < u.Size(); ++j)
{
u[j] = LogisticStep(u[j], alpha, dt);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, StepV i)
{
const double dt = dt_func_v(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "Size mismatch.");
for (int j = 0; j < lambda.Size(); ++j)
{
lambda[j] *= LogisticJac(u_i[j], alpha, dt);
}
};
mfem::Vector u0v(n), ut(n);
for (int j = 0; j < n; ++j) { u0v[j] = 0.2 + 0.05 * std::cos(0.7*(j+1)); }
ut = target_v;
mfem::Vector u = u0v;
double t_phys = 0.0;
StepV i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func_v(i);
++i;
}
const StepV m = i;
const mfem::Vector u_m = u;
mfem::Vector diff(u_m);
diff -= ut;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
// backward
mfem::Vector lambda = diff; // terminal adjoint
mfem::Vector u_work(n);
for (StepV j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
// directional FD check
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3*(j+1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
StepV k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func_v(k);
++k;
}
mfem::Vector dd(uu);
dd -= ut;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0v), u_minus(u0v);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
mfem::out << "\n[FileIO][Vector]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_dir_adj - dJ_dir_fd) << "\n";
mfem::out << " ||u_m||_2 = " << u_m.Norml2() << "\n";
mfem::out << " ||u_m - u_target||2 = " << diff.Norml2() << "\n";
}
mfem::out << "\nDone.\n";
return 0;
}
@@ -0,0 +1,375 @@
#include "mfem.hpp"
#include "dynamic_checkpointing.hpp"
#include "fixed_slot_checkpoint_storage.hpp"
#include <cmath>
#include <iomanip>
#include <string>
using namespace mfem;
// Logistic ODE (scalar or componentwise):
// du/dt = alpha*u*(1-u)
// Explicit Euler:
// u_{i+1} = u_i + dt_i * alpha*u_i*(1-u_i)
//
// Objective:
// Scalar: J = 0.5*(u_m - target)^2
// Vector: J = 0.5*||u_m - u_target||^2
//
// Discrete adjoint:
// dF/du = 1 + dt_i*alpha*(1 - 2*u_i)
// lambda_i = (dF/du at u_i) * lambda_{i+1}
static inline double LogisticStep(const double u, const double alpha, const double dt)
{
return u + dt * alpha * u * (1.0 - u);
}
static inline double LogisticJac(const double u, const double alpha, const double dt)
{
return 1.0 + dt * alpha * (1.0 - 2.0*u);
}
// --------------------------
// Scalar run (double)
// --------------------------
template <typename Storage>
static void RunScalarFixedSlot(int s,
Storage &storage,
double alpha,
double dt0,
double omega,
double Tfinal,
double u0,
double target,
double eps)
{
using CKPT = mfem::DynamicCheckpointing<double, Storage>;
using Step = typename CKPT::Step;
CKPT ckpt(s, storage);
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const double &u) { return u; };
auto restore_snapshot = [](const double &snap, double &out) { out = snap; };
auto primal_step = [&](double &u, Step i)
{
const double dt = dt_func(i);
u = LogisticStep(u, alpha, dt);
};
auto adjoint_step = [&](double &lambda, const double &u_i, Step i)
{
const double dt = dt_func(i);
lambda *= LogisticJac(u_i, alpha, dt);
};
// Forward sweep (unknown m; stop on accumulated physical time)
double u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target) * (u_m - target);
// Backward sweep
MFEM_VERIFY(m > 0, "Scalar run produced m=0 steps.");
double lambda = (u_m - target); // terminal condition dJ/du_m
double u_work = 0.0;
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const double dJ_du0_adj = lambda;
// FD check
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
uu = LogisticStep(uu, alpha, dt_func(k));
tt += dt_func(k);
++k;
}
const double r = uu - target;
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
mfem::out << "\n[FixedSlot][Scalar]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " dJ/du0 adjoint = " << dJ_du0_adj << "\n";
mfem::out << " dJ/du0 FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_du0_adj - dJ_du0_fd) << "\n";
}
// --------------------------
// Vector run (mfem::Vector)
// --------------------------
template <typename Storage>
static void RunVectorFixedSlot(int s,
Storage &storage,
int n,
double alpha,
double dt0,
double omega,
double Tfinal,
double target_val,
double eps)
{
using CKPT = mfem::DynamicCheckpointing<mfem::Vector, Storage>;
using Step = typename CKPT::Step;
CKPT ckpt(s, storage);
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const mfem::Vector &u) { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &out) { out = snap; };
auto primal_step = [&](mfem::Vector &u, Step i)
{
const double dt = dt_func(i);
for (int j = 0; j < u.Size(); ++j)
{
u[j] = LogisticStep(u[j], alpha, dt);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, Step i)
{
const double dt = dt_func(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "lambda and u_i size mismatch.");
for (int j = 0; j < lambda.Size(); ++j)
{
lambda[j] *= LogisticJac(u_i[j], alpha, dt);
}
};
// Initial condition and target
mfem::Vector u0(n), u_target(n);
for (int j = 0; j < n; ++j)
{
u0[j] = 0.2 + 0.05 * std::cos(0.7 * (j + 1));
}
u_target = target_val;
// Forward sweep
mfem::Vector u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const mfem::Vector u_m = u;
mfem::Vector diff(u_m);
diff -= u_target;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
// Backward sweep
MFEM_VERIFY(m > 0, "Vector run produced m=0 steps.");
mfem::Vector lambda = diff; // terminal adjoint = dJ/du_m
mfem::Vector u_work(n);
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
// Directional FD check
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3 * (j + 1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func(k);
++k;
}
mfem::Vector dd(uu);
dd -= u_target;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0), u_minus(u0);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
mfem::out << "\n[FixedSlot][Vector]\n";
mfem::out << " n = " << n << "\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " ||u_m||_2 = " << u_m.Norml2() << "\n";
mfem::out << " ||u_m-u_target|| = " << diff.Norml2() << "\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_dir_adj - dJ_dir_fd) << "\n";
}
int main(int argc, char *argv[])
{
// Backend selection:
// 0 = fixed-slot memory (single RAM block)
// 1 = fixed-slot file (single file with fixed offsets)
int backend = 0;
// Common parameters
int s = 8; // number of REAL stored checkpoints
double alpha = 2.0;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
double eps = 1e-7;
// Scalar parameters
double u0 = 0.2;
double target_s = 0.7;
// Vector parameters
int n = 64; // must stay fixed for fixed-slot vector packing
double target_v = 0.7;
// File backend parameters
std::string scalar_file = "scalar_fixedslots.bin";
std::string vector_file = "vector_fixedslots.bin";
bool truncate_files = true;
bool flush_on_store = true; // safer for demo correctness with iostreams
OptionsParser args(argc, argv);
args.AddOption(&backend, "-b", "--backend",
"Backend: 0=memory fixed-slots, 1=file fixed-slots (single file).");
args.AddOption(&s, "-s", "--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0", "Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega", "Omega for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal", "Stop when accumulated time reaches Tfinal.");
args.AddOption(&eps, "-eps", "--fd-eps", "FD epsilon.");
args.AddOption(&u0, "-u0", "--u0", "Scalar initial condition u0.");
args.AddOption(&target_s, "-ts", "--target-scalar", "Scalar target.");
args.AddOption(&n, "-n", "--size", "Vector dimension (fixed).");
args.AddOption(&target_v, "-tv", "--target-vector", "Vector target value per component.");
args.AddOption(&scalar_file, "-sf", "--scalar-file", "File for scalar fixed-slot storage.");
args.AddOption(&vector_file, "-vf", "--vector-file", "File for vector fixed-slot storage.");
args.AddOption(&truncate_files, "-tr", "--truncate", "-ntr", "--no-truncate",
"Truncate checkpoint files on startup.");
args.AddOption(&flush_on_store, "-fl", "--flush", "-nfl", "--no-flush",
"Flush on each Store() (demo-safety; slower).");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
mfem::out << std::setprecision(15);
if (backend == 0)
{
mfem::out << "\nUsing fixed-slot MEMORY backend (single RAM block)\n";
// Scalar: fixed-size POD => trivial packer
mfem::FixedSlotMemoryCheckpointStorage<double> stor_s(s);
RunScalarFixedSlot(s, stor_s, alpha, dt0, omega, Tfinal, u0, target_s, eps);
// Vector: fixed-size packing (n must remain constant)
mfem::FixedVectorPacker packer(n);
mfem::FixedSlotMemoryCheckpointStorage<mfem::Vector, mfem::FixedVectorPacker> stor_v(s, packer);
RunVectorFixedSlot(s, stor_v, n, alpha, dt0, omega, Tfinal, target_v, eps);
}
else if (backend == 1)
{
mfem::out << "\nUsing fixed-slot FILE backend (single file with fixed offsets)\n";
// Scalar file
mfem::FixedSlotFileCheckpointStorage<double> stor_s(scalar_file, s,
mfem::TrivialFixedPacker<double>(),
truncate_files,
flush_on_store);
RunScalarFixedSlot(s, stor_s, alpha, dt0, omega, Tfinal, u0, target_s, eps);
// Vector file (fixed-size packing with n)
mfem::FixedVectorPacker packer(n);
mfem::FixedSlotFileCheckpointStorage<mfem::Vector, mfem::FixedVectorPacker>
stor_v(vector_file, s, packer, truncate_files, flush_on_store);
RunVectorFixedSlot(s, stor_v, n, alpha, dt0, omega, Tfinal, target_v, eps);
}
else
{
MFEM_ABORT("Unknown backend. Use -b 0 (memory) or -b 1 (file).");
}
mfem::out << "\nDone.\n";
return 0;
}
@@ -0,0 +1,178 @@
#include "mfem.hpp"
#include "revolve_checkpointing.hpp"
#include <cmath>
#include <iomanip>
#include <string>
using namespace mfem;
// the forward state consists of vector and
// several additional values
struct State
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
mfem::Vector v;
};
/// class to manage the Snaphot Layout
class StateSnapshotLayout
{
public:
// Constructor: n - size of the vector in the state
explicit StateSnapshotLayout(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "StateSnapshotLayout: n must be > 0.");
}
// returns the total size of the snapshot in mfem::real_t
int N() const { return n_; }
// returns the total number of bytes necessary to store the state
int Bytes() const
{
return (2 + n_) * (int)sizeof(mfem::real_t);
}
// Pack State -> bytes (host bytes)
void Pack(const State &s, unsigned char *dst, int bytes) const
{
MFEM_VERIFY(dst != nullptr, "Pack: dst is null.");
MFEM_VERIFY(bytes == Bytes(), "Pack: snapshot byte size mismatch.");
MFEM_VERIFY(s.v.Size() == n_, "Pack: State.v size mismatch.");
// Ensure we read from host even if MFEM uses device memory.
const mfem::real_t *vh = s.v.HostRead();
std::memcpy(dst + 0*sizeof(mfem::real_t), &s.time, sizeof(mfem::real_t));
std::memcpy(dst + 1*sizeof(mfem::real_t), &s.obj, sizeof(mfem::real_t));
std::memcpy(dst + 2*sizeof(mfem::real_t),
vh, (std::size_t)n_ * sizeof(mfem::real_t));
}
// Unpack bytes -> State (host write)
void Unpack(const unsigned char *src, int bytes, State &out) const
{
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
MFEM_VERIFY(bytes == Bytes(), "Unpack: snapshot byte size mismatch.");
if (out.v.Size() != n_) { out.v.SetSize(n_); }
mfem::real_t *vh = out.v.HostWrite();
std::memcpy(&out.time, src + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(&out.obj, src + 1*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(vh,
src + 2*sizeof(mfem::real_t),
(std::size_t)n_ * sizeof(mfem::real_t));
}
private:
int n_ = 0;
};
/* The REVOLVE manager (as provided earlier) uses this callback style:
* make_snapshot(const State&, uint8_t* out, size_t bytes)
* restore_snapshot(State&, const uint8_t* in, size_t bytes)
* primal_step(int step, State&)
* adjoint_step(int step, const State&, AdjointState&)
and a storage backend with:
* Save(slot, bytes)
* Load(slot, bytes)
REVOLVE manager will:
*request storing snapshots into checkpoint slots (takeshot)
*request restoring a checkpoint slot (restore)
*request recomputation forward (advance)
*then request the next adjoint step (firsturn / youturn)
all while using only (Ncheck) stored checkpoints.
Fixed-step REVOLVE manager:
* Snapshot is raw bytes packed/unpacked by your callbacks.
* Storage is a fixed indexed array (Save(slot) / Load(slot)),
because REVOLVE addresses checkpoints by slot index.
Best use when Nsteps is known in the begining of the simulation.
*/
int main(int argc, char *argv[])
{
// Backend selection:
// 0 = fixed-slot memory (single RAM block)
// 1 = fixed-slot file (single file with fixed offsets)
int backend = 0;
const int n = 100/* fixed State.v size */;
StateSnapshotLayout layout(n);
const std::size_t snapshot_bytes = (std::size_t)layout.Bytes();
const int Nsteps = 20 /* known number of time steps */;
const int Ncheck = 5 /* number of checkpoints (snaps) */;
// Memory backend (single block)
// FixedSlotMemoryStorage storage(Ncheck, snapshot_bytes);
// FixedStepRevolveCheckpointing<FixedSlotMemoryStorage>
// cktp(Nsteps, Ncheck, snapshot_bytes, storage);
// or file backend (single file)
FixedSlotFileStorage storage("revolve_ckpts.bin", Ncheck, snapshot_bytes);
FixedStepRevolveCheckpointing<FixedSlotFileStorage>
cktp(Nsteps, Ncheck, snapshot_bytes, storage);
auto make_snapshot = [&](const State &s, uint8_t *outb, std::size_t bytes)
{
MFEM_VERIFY(bytes == snapshot_bytes, "make_snapshot: byte size mismatch");
layout.Pack(s, reinterpret_cast<unsigned char*>(outb), (int)bytes);
};
auto restore_snapshot = [&](State &s, const uint8_t *inb, std::size_t bytes)
{
MFEM_VERIFY(bytes == snapshot_bytes, "restore_snapshot: byte size mismatch");
layout.Unpack(reinterpret_cast<const unsigned char*>(inb), (int)bytes, s);
};
// set the work state
State u_work;
u_work.v.SetSize(n); u_work.v=0.0;
State u;
u.v.SetSize(n); u.v=0.0; u.obj=0.0; u.time=0.0;
// init u.time, u.obj, u.v ...
auto primal_step = [&](int step, State &u_inout)
{
(void)step;
// advance u_inout -> u_{step+1}
};
struct AdjointState { /* ... */ };
AdjointState lambda;
auto adjoint_step = [&](int step, const State &u_step, AdjointState &lambda_inout)
{
(void)step;
// update lambda_{step+1} -> lambda_step using u_step
};
// Forward: i = 0..Nsteps-1
for (int i = 0; i < Nsteps; ++i)
{
cktp.ForwardStep(i, u, primal_step, make_snapshot);
}
// Reverse: i = Nsteps-1..0
for (int i = Nsteps - 1; i >= 0; --i)
{
cktp.BackwardStep(i, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
}
mfem::out << "\nDone.\n";
return 0;
};
+163
View File
@@ -0,0 +1,163 @@
#include "mfem.hpp"
#include "dynamic_checkpointing.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
// Scalar logistic ODE:
// du/dt = alpha*u*(1-u)
// Explicit Euler with variable dt(i):
// u_{i+1} = u_i + dt_i*alpha*u_i*(1-u_i)
// Objective:
// J = 0.5*(u_m - target)^2
// Discrete adjoint (scalar):
// lambda_m = (u_m - target)
// lambda_i = (dF/du at u_i)^T * lambda_{i+1}
// dF/du = 1 + dt_i*alpha*(1 - 2*u_i)
int main(int argc, char *argv[])
{
int s = 3;
double alpha = 2.0;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
double u0 = 0.2;
double target = 0.7;
double eps = 1e-7;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0", "Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega", "Omega in dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal", "Terminate when accumulated time reaches Tfinal.");
args.AddOption(&u0, "-u0", "--u0", "Initial scalar state u0.");
args.AddOption(&target, "-ut", "--target", "Target value in J=0.5*(u_m-target)^2.");
args.AddOption(&eps, "-eps", "--fd-eps", "Finite-difference epsilon.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
using Step = mfem::DynamicCheckpointing<double>::Step;
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
// Checkpoint manager:
// State = double
// Snapshot= double
mfem::DynamicCheckpointing<double> ckpt(s);
auto make_snapshot = [](const double &u) -> double { return u; };
auto restore_snapshot = [](const double &snap, double &out) { out = snap; };
auto primal_step = [&](double &u, Step i)
{
const double dt = dt_func(i);
u = u + dt * alpha * u * (1.0 - u);
};
auto adjoint_step = [&](double &lambda, const double &u_i, Step i)
{
const double dt = dt_func(i);
const double dF_du = 1.0 + dt * alpha * (1.0 - 2.0 * u_i);
lambda = dF_du * lambda;
};
// ---------------- Forward sweep (unknown m) ----------------
double u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target) * (u_m - target);
mfem::out << std::setprecision(15);
mfem::out << "\n[Scalar] Forward finished:\n";
mfem::out << " m (steps) = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n\n";
mfem::out << "[Scalar] Checkpoint set after forward sweep (step, level, stored):\n";
for (const auto &cp : ckpt.GetCheckpointInfo())
{
mfem::out << " step=" << cp.step
<< ", level=" << cp.level
<< ", stored=" << (cp.stored ? "yes" : "no")
<< (cp.stored ? "" : " (placeholder)")
<< "\n";
}
mfem::out << "\n";
MFEM_VERIFY(m > 0, "Forward produced m=0 steps; nothing to do.");
// ---------------- Backward sweep (adjoint) ----------------
double lambda = (u_m - target); // terminal adjoint = dJ/du_m
double u_work = 0.0; // scratch primal state u_i
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; } // avoid signed underflow
}
const double dJ_du0_adjoint = lambda;
// ---------------- Finite-difference gradient check ----------------
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func(k);
++k;
}
const double r = (uu - target);
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
const double abs_err = std::abs(dJ_du0_adjoint - dJ_du0_fd);
const double rel_err = abs_err / (std::abs(dJ_du0_fd) + 1e-30);
mfem::out << "[Scalar] Gradient check (dJ/du0):\n";
mfem::out << " adjoint = " << dJ_du0_adjoint << "\n";
mfem::out << " FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << abs_err << "\n";
mfem::out << " rel err = " << rel_err << "\n\n";
return 0;
}
@@ -0,0 +1,306 @@
#include "mfem.hpp"
#include "dynamic_checkpointing.hpp"
#include "segment_checkpoint_storage.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
static double LogisticStep(double u, double alpha, double dt)
{
return u + dt * alpha * u * (1.0 - u);
}
static double LogisticJac(double u, double alpha, double dt)
{
return 1.0 + dt * alpha * (1.0 - 2.0*u);
}
#if MFEM_HAVE_FILESYSTEM
static void PurgeDir(const std::string &dir)
{
std::error_code ec;
mfem_fs::remove_all(mfem_fs::path(dir), ec);
mfem_fs::create_directories(mfem_fs::path(dir), ec);
MFEM_VERIFY(!ec, "Failed to purge/create directory.");
}
#endif
int main(int argc, char *argv[])
{
int s = 3;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
// scalar params
double alpha = 2.0;
double u0 = 0.2;
double target_s = 0.7;
double eps = 1e-7;
// vector params
int n = 16;
double target_v = 0.7;
// segmented storage params
std::string dir_scalar = "chk_scalar_segments";
std::string dir_vector = "chk_vector_segments";
long long records_per_file = 4096;
bool keep_files = false;
bool purge_dirs = true;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&dt0, "-dt0", "--dt0", "Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega", "Omega for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal", "Stop when accumulated time reaches Tfinal.");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&u0, "-u0", "--u0", "Scalar initial u0.");
args.AddOption(&target_s, "-ts", "--target-scalar", "Scalar target.");
args.AddOption(&eps, "-eps", "--fd-eps", "FD epsilon (scalar and vector directional).");
args.AddOption(&n, "-n", "--size", "Vector dimension.");
args.AddOption(&target_v, "-tv", "--target-vector", "Vector target value per component.");
args.AddOption(&dir_scalar, "-ds", "--dir-scalar", "Directory for scalar segment files.");
args.AddOption(&dir_vector, "-dv", "--dir-vector", "Directory for vector segment files.");
args.AddOption(&records_per_file, "-rpf", "--records-per-file",
"How many handles belong to one segment file (range size).");
args.AddOption(&keep_files, "-k", "--keep-files", "-nk", "--no-keep-files",
"Keep segment files (debug).");
args.AddOption(&purge_dirs, "-p", "--purge-dirs", "-np", "--no-purge-dirs",
"Purge checkpoint directories at start (recommended).");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(records_per_file > 0, "Need records_per_file > 0.");
#if MFEM_HAVE_FILESYSTEM
if (purge_dirs)
{
PurgeDir(dir_scalar);
PurgeDir(dir_vector);
}
#else
MFEM_VERIFY(!purge_dirs, "purge_dirs requires <filesystem> support.");
#endif
using StepS = mfem::DynamicCheckpointing<
double,
mfem::SegmentedFileCheckpointStorage<double>>::Step;
auto dt_func = [&](StepS i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
mfem::out << std::setprecision(15);
// ============================================================
// A) Scalar with segmented storage
// ============================================================
mfem::SegmentedFileCheckpointStorage<double> stor_s(dir_scalar, records_per_file,
"seg_", ".bin", true, keep_files);
{
mfem::DynamicCheckpointing<double, mfem::SegmentedFileCheckpointStorage<double>> ckpt(s, stor_s);
auto make_snapshot = [](const double &u) { return u; };
auto restore_snapshot = [](const double &snap, double &out) { out = snap; };
auto primal_step = [&](double &u, StepS i)
{
u = LogisticStep(u, alpha, dt_func(i));
};
auto adjoint_step = [&](double &lambda, const double &u_i, StepS i)
{
lambda *= LogisticJac(u_i, alpha, dt_func(i));
};
double u = u0;
double t_phys = 0.0;
StepS i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const StepS m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target_s) * (u_m - target_s);
double lambda = (u_m - target_s);
double u_work = 0.0;
for (StepS j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const double dJ_du0_adj = lambda;
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
StepS k = 0;
while (tt < Tfinal)
{
uu = LogisticStep(uu, alpha, dt_func(k));
tt += dt_func(k);
++k;
}
const double r = uu - target_s;
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
mfem::out << "\n[SegmentIO][Scalar]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " dJ/du0 adjoint = " << dJ_du0_adj << "\n";
mfem::out << " dJ/du0 FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_du0_adj - dJ_du0_fd) << "\n";
}
// ============================================================
// B) Vector with segmented storage
// ============================================================
mfem::SegmentedFileCheckpointStorage<mfem::Vector> stor_v(dir_vector, records_per_file,
"seg_", ".bin", true, keep_files);
{
mfem::DynamicCheckpointing<mfem::Vector, mfem::SegmentedFileCheckpointStorage<mfem::Vector>>
ckpt(s, stor_v);
using StepV = mfem::DynamicCheckpointing<
mfem::Vector,
mfem::SegmentedFileCheckpointStorage<mfem::Vector>>::Step;
auto dt_func_v = [&](StepV i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const mfem::Vector &u) { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &out) { out = snap; };
auto primal_step = [&](mfem::Vector &u, StepV i)
{
const double dt = dt_func_v(i);
for (int j = 0; j < u.Size(); ++j)
{
u[j] = LogisticStep(u[j], alpha, dt);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, StepV i)
{
const double dt = dt_func_v(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "Size mismatch.");
for (int j = 0; j < lambda.Size(); ++j)
{
lambda[j] *= LogisticJac(u_i[j], alpha, dt);
}
};
mfem::Vector u0v(n), ut(n);
for (int j = 0; j < n; ++j) { u0v[j] = 0.2 + 0.05 * std::cos(0.7*(j+1)); }
ut = target_v;
mfem::Vector u = u0v;
double t_phys = 0.0;
StepV i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func_v(i);
++i;
}
const StepV m = i;
mfem::Vector diff(u);
diff -= ut;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
mfem::Vector lambda = diff;
mfem::Vector u_work(n);
for (StepV j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3*(j+1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
StepV k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func_v(k);
++k;
}
mfem::Vector dd(uu);
dd -= ut;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0v), u_minus(u0v);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
mfem::out << "\n[SegmentIO][Vector]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_dir_adj - dJ_dir_fd) << "\n";
mfem::out << " ||u - u_target||2 = " << diff.Norml2() << "\n";
}
// Optional cleanup: delete segment files this run touched
if (!keep_files)
{
stor_s.PurgeAllFiles();
stor_v.PurgeAllFiles();
}
mfem::out << "\nDone.\n";
return 0;
}
+193
View File
@@ -0,0 +1,193 @@
#include "mfem.hpp"
#include "dynamic_checkpointing.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
// Vector logistic ODE (componentwise):
// du/dt = alpha*u*(1-u) (applied to each component)
// Explicit Euler:
// u_{i+1}[j] = u_i[j] + dt_i*alpha*u_i[j]*(1-u_i[j])
// Objective:
// J = 0.5*||u_m - u_target||^2
// Terminal adjoint:
// lambda_m = u_m - u_target
// Adjoint step (componentwise):
// lambda_i[j] = (1 + dt_i*alpha*(1 - 2*u_i[j])) * lambda_{i+1}[j]
int main(int argc, char *argv[])
{
int s = 3;
int n = 16;
double alpha = 2.0;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
double target_val = 0.7;
double eps = 1e-7;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&n, "-n", "--size", "Vector dimension n.");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0", "Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega", "Omega in dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal", "Terminate when accumulated time reaches Tfinal.");
args.AddOption(&target_val,"-tv", "--target", "Target value for each component.");
args.AddOption(&eps, "-eps", "--fd-eps", "Finite-difference epsilon (directional).");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
using Step = mfem::DynamicCheckpointing<mfem::Vector>::Step;
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
// Checkpoint manager:
// State = mfem::Vector
// Snapshot= mfem::Vector
mfem::DynamicCheckpointing<mfem::Vector> ckpt(s);
auto make_snapshot = [](const mfem::Vector &u) -> mfem::Vector { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &out) { out = snap; };
auto primal_step = [&](mfem::Vector &u, Step i)
{
const double dt = dt_func(i);
for (int j = 0; j < u.Size(); ++j)
{
const double uj = u[j];
u[j] = uj + dt * alpha * uj * (1.0 - uj);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, Step i)
{
const double dt = dt_func(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "lambda and u_i size mismatch");
for (int j = 0; j < lambda.Size(); ++j)
{
const double dF_du = 1.0 + dt * alpha * (1.0 - 2.0 * u_i[j]);
lambda[j] *= dF_du;
}
};
// Initial condition and target
mfem::Vector u0(n), u_target(n);
for (int j = 0; j < n; ++j)
{
u0[j] = 0.2 + 0.05 * std::cos(0.7 * (j + 1));
}
u_target = target_val;
// ---------------- Forward sweep (unknown m) ----------------
mfem::Vector u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const mfem::Vector u_m = u;
mfem::Vector diff(u_m);
diff -= u_target;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
mfem::out << std::setprecision(15);
mfem::out << "\n[Vector] Forward finished:\n";
mfem::out << " m (steps) = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " ||u_m|| = " << u_m.Norml2() << "\n";
mfem::out << " ||u_m-ut||= " << diff.Norml2() << "\n\n";
mfem::out << "[Vector] Checkpoint set after forward sweep (step, level, stored):\n";
for (const auto &cp : ckpt.GetCheckpointInfo())
{
mfem::out << " step=" << cp.step
<< ", level=" << cp.level
<< ", stored=" << (cp.stored ? "yes" : "no")
<< (cp.stored ? "" : " (placeholder)")
<< "\n";
}
mfem::out << "\n";
MFEM_VERIFY(m > 0, "Forward produced m=0 steps; nothing to do.");
// ---------------- Backward sweep (adjoint) ----------------
mfem::Vector lambda = diff; // terminal = dJ/du_m
mfem::Vector u_work(n); // scratch primal u_i
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
// ---------------- Directional FD check ----------------
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3 * (j + 1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func(k);
++k;
}
mfem::Vector dd(uu);
dd -= u_target;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0), u_minus(u0);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
const double abs_err = std::abs(dJ_dir_adj - dJ_dir_fd);
const double rel_err = abs_err / (std::abs(dJ_dir_fd) + 1e-30);
mfem::out << "[Vector] Directional derivative check:\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << abs_err << "\n";
mfem::out << " rel err = " << rel_err << "\n\n";
return 0;
}
+42
View File
@@ -0,0 +1,42 @@
# 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.
if(MFEM_USE_MPI)
list(APPEND EX_SOURCES linear_elasticity.hpp)
list(APPEND EX_HEADERS linear_elasticity.cpp)
convert_filenames_to_full_paths(EX_SOURCES)
convert_filenames_to_full_paths(EX_HEADERS)
set(EX_COMMON_FILES
EXTRA_SOURCES ${EX_SOURCES}
EXTRA_HEADERS ${EX_HEADERS})
add_mfem_miniapp(mtop_test_forward_le
MAIN test_forward_le.cpp
${EX_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_adjoint_le
MAIN test_adjoint_le.cpp
${EX_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_test_adjoint_op
MAIN test_adjoint_op.cpp
${EX_COMMON_FILES}
LIBRARIES mfem)
endif (MFEM_USE_MPI)
+555
View File
@@ -0,0 +1,555 @@
$MeshFormat
2.2 0 8
$EndMeshFormat
$Nodes
267
1 -5 -5 0
2 5 -5 0
3 5 0 0
4 3 0 0
5 2.5 0 0
6 0.25 0 0
7 -0.25 0 0
8 -2.5 0 0
9 -3 0 0
10 -5 0 0
11 -4.5 -5 0
12 -4 -5 0
13 -3.5 -5 0
14 -3 -5 0
15 -2.5 -5 0
16 -2 -5 0
17 -1.5 -5 0
18 -1 -5 0
19 -0.5 -5 0
20 0 -5 0
21 0.5 -5 0
22 1 -5 0
23 1.5 -5 0
24 2 -5 0
25 2.5 -5 0
26 3 -5 0
27 3.5 -5 0
28 4 -5 0
29 4.5 -5 0
30 5 -4.5 0
31 5 -4 0
32 5 -3.5 0
33 5 -3 0
34 5 -2.5 0
35 5 -2 0
36 5 -1.5 0
37 5 -1 0
38 5 -0.5 0
39 4.5 0 0
40 4 0 0
41 3.5 0 0
42 2.75 0 0
43 2.125 0 0
44 1.75 0 0
45 1.375 0 0
46 1 0 0
47 0.625 0 0
48 0 0 0
49 -0.625 0 0
50 -1 0 0
51 -1.375 0 0
52 -1.75 0 0
53 -2.125 0 0
54 -2.75 0 0
55 -3.5 0 0
56 -4 0 0
57 -4.5 0 0
58 -5 -0.5 0
59 -5 -1 0
60 -5 -1.5 0
61 -5 -2 0
62 -5 -2.5 0
63 -5 -3 0
64 -5 -3.5 0
65 -5 -4 0
66 -5 -4.5 0
67 3.147692481109132e-08 -2.865638549497702 0
68 0.7411835869316199 -1.439094140802561 0
69 -0.7411835350444509 -1.43909394139115 0
70 4.165134723562585e-08 -1.07528086308038 0
71 0.6217064619398043 -3.063797051940143 0
72 1.152592324474185 -3.183434480740315 0
73 1.65143516768565 -3.26705431245132 0
74 2.136365371184163 -3.328666155776893 0
75 2.615028312537451 -3.375240988133206 0
76 3.09130721617413 -3.411145687677366 0
77 3.567287338332451 -3.439448189733019 0
78 4.043963758935188 -3.462490104151034 0
79 4.521595026340442 -3.482145270350558 0
80 4.53664432455652 -1.978600022703204 0
81 4.07646042959763 -1.955552774759151 0
82 3.622473657533698 -1.929092238665446 0
83 3.177227845469996 -1.897332899989072 0
84 2.741992072859029 -1.858107555374913 0
85 2.315943299837527 -1.808659406432935 0
86 1.898361004221733 -1.745502414617874 0
87 1.49145086264214 -1.664634388017854 0
88 1.101775323220076 -1.562562613115749 0
89 0.353776448027755 -2.251637070635797 0
90 0.5817531180500853 -1.81803370667541 0
91 5.893865315616141e-08 -3.688498271511504 0
92 4.16589230598604e-08 -4.361309501422365 0
93 -0.6217063447015869 -3.063796759607709 0
94 -1.152592159125127 -3.183434038351562 0
95 -1.651434963884719 -3.267053810340575 0
96 -2.136365141575386 -3.328665661557225 0
97 -2.61502807124587 -3.375240540152028 0
98 -3.091306983305333 -3.411145327000243 0
99 -3.567287131691835 -3.439447930570905 0
100 -4.043963594137694 -3.462489938776664 0
101 -4.521594918346396 -3.482145167593328 0
102 -0.3537764846052657 -2.25163666228703 0
103 -0.5817531278520051 -1.818033637502388 0
104 -4.536644079337935 -1.978599647807268 0
105 -4.076459846340027 -1.955551693229819 0
106 -3.62247296968287 -1.929090872966037 0
107 -3.177227175417933 -1.897331476171343 0
108 -2.741991512175473 -1.858106255217367 0
109 -2.315942871426326 -1.808658299363782 0
110 -1.898360705274605 -1.745501533735374 0
111 -1.491450676964205 -1.664633739553477 0
112 -1.101775219860154 -1.562562160656453 0
113 0.4257768789585238 -1.302334772873867 0
114 0.1721672606205888 -1.17264922719834 0
115 -0.4257768283604875 -1.302334697557239 0
116 -0.1721671949129244 -1.172649169444562 0
117 5.039529297540552e-08 -0.8795314788631696 0
118 4.399248353403805e-08 -0.6140653765645073 0
119 2.391773920807489e-08 -0.3141333775040666 0
120 -0.8478847531712174 -1.078320812844798 0
121 -0.9182559238223678 -0.7209963330169558 0
122 -0.9651087839812744 -0.3619977550022322 0
123 0.8478848525935928 -1.078321055602277 0
124 0.9182560388765455 -0.7209965569029586 0
125 0.9651088678468086 -0.3619978973464902 0
126 0.8006710117138355 -2.47676624955276 0
127 0.9687509599230363 -1.991490373658575 0
128 1.267199118780179 -2.624331559216512 0
129 1.386971133363275 -2.122717577561095 0
130 1.733121009957757 -2.726002099497514 0
131 1.819410451523041 -2.220141767339956 0
132 2.196724740979399 -2.79935877814003 0
133 2.258923990363529 -2.292999983602781 0
134 2.659538340537992 -2.854063574089099 0
135 2.703484150533682 -2.348367764794451 0
136 3.123294379466398 -2.895966051131377 0
137 3.153232485979804 -2.391299851091255 0
138 3.589167673689383 -2.928956418122387 0
139 3.608560261518269 -2.425426828148212 0
140 4.057538435196422 -2.955867592895155 0
141 4.069039111160356 -2.453487152690021 0
142 4.528077399266859 -2.978924099167553 0
143 4.533425113711518 -2.477698347634654 0
144 0.5529057974518461 -3.729502166598679 0
145 1.08019255813471 -3.784879970197416 0
146 1.588026898054561 -3.835138377533823 0
147 2.08405323877662 -3.876448947726767 0
148 2.573544992310209 -3.909301557064001 0
149 3.059798426647599 -3.93522570140068 0
150 3.544781312614329 -3.955866580867792 0
151 4.029558163742809 -3.972704995723558 0
152 4.514598481900658 -3.98704568763109 0
153 0.5207429539874712 -4.372050944340369 0
154 1.034280493174569 -4.392852449659338 0
155 1.539989110360686 -4.415218583983825 0
156 2.039762094695762 -4.435343846034065 0
157 2.535732622385146 -4.452137363307919 0
158 3.029570711380306 -4.465715840583259 0
159 3.522401092498168 -4.476640912585935 0
160 4.014893345389466 -4.485580909772104 0
161 4.507386261505489 -4.493183790689629 0
162 -0.5529056692413425 -3.729501971224406 0
163 -0.5207428615254834 -4.372050820213267 0
164 -1.080192412465082 -3.784879670455047 0
165 -1.034280383041777 -4.392852235768746 0
166 -1.588026734554675 -3.835138038639476 0
167 -1.539988984956153 -4.4152183290159 0
168 -2.084053063491914 -3.876448618727172 0
169 -2.039761962697679 -4.435343595363031 0
170 -2.57354481360955 -3.909301269424566 0
171 -2.535732489384031 -4.452137137805948 0
172 -3.059798253458965 -3.935225467144031 0
173 -3.02957058029087 -4.465715643196338 0
174 -3.544781151325922 -3.955866397178196 0
175 -3.52240096857565 -4.476640744295789 0
176 -4.029558025213573 -3.97270485120735 0
177 -4.014893239369709 -4.485580776942857 0
178 -4.514598383857045 -3.987045581044113 0
179 -4.507386188672238 -4.493183701962657 0
180 -0.8006709540149735 -2.476765813610716 0
181 -1.267198951523049 -2.624330912705657 0
182 -1.733120766602745 -2.726001387393099 0
183 -2.196724444010224 -2.799358049188522 0
184 -2.659538010646273 -2.854062873235562 0
185 -3.123294047769952 -2.895965440056678 0
186 -3.589167377515512 -2.928955953414885 0
187 -4.057538220541448 -2.955867327310949 0
188 -4.528077277925624 -2.978923981788693 0
189 -0.9687509231976139 -1.991490069922028 0
190 -1.386970993902387 -2.122717016201259 0
191 -1.819410201177847 -2.220141006425682 0
192 -2.258923647273578 -2.292999103541908 0
193 -2.703483736760897 -2.348366833255585 0
194 -3.153232042234145 -2.391298962173866 0
195 -3.608559855392381 -2.425426109249888 0
196 -4.069038820050712 -2.453486730829595 0
197 -4.533424988551208 -2.47769824111193 0
198 -1.272476666753393e-08 -1.924359482524905 0
199 -0.2533343974762239 -1.606443202429349 0
200 0.2533344229837241 -1.606443132326576 0
201 2.53249852051541e-08 -1.388593389582041 0
202 -0.5272934750247175 -0.9926764285952894 0
203 -0.5858028437947259 -0.6707709234636405 0
204 -0.6157803404632994 -0.3382179577638268 0
205 -0.2456717774258411 -0.9190691202974864 0
206 -0.2802909995762338 -0.6309898154192547 0
207 -0.2874733536873217 -0.3203514901649924 0
208 0.5272935606350887 -0.9926765509995065 0
209 0.2456718710348613 -0.9190692014700954 0
210 0.5858029352058564 -0.6707710264887236 0
211 0.2802910897441391 -0.6309898869990653 0
212 0.6157804025422635 -0.3382180183964293 0
213 0.2874734069247128 -0.3203515208733445 0
214 -4.536347854639426 -1.481553666129692 0
215 -4.530849447494669 -0.9863227548585151 0
216 -4.518663750100603 -0.4925991230550557 0
217 -4.076965269089723 -1.461824093896926 0
218 -4.066822817654915 -0.9717378202251115 0
219 -4.041844794305193 -0.4846493859382576 0
220 -3.626631555456245 -1.439400263472837 0
221 -3.614767853477897 -0.9552829042038593 0
222 -3.577234059246457 -0.4756361204437503 0
223 -3.190066245774652 -1.412949304825956 0
224 -3.183592242159449 -0.9362633794796755 0
225 -3.139804799022206 -0.465366666148178 0
226 -2.770239834557847 -1.381149002386727 0
227 -2.78361605771829 -0.9144286373168963 0
228 -2.777303799227714 -0.454899765247956 0
229 -2.363294730094991 -1.342156913093806 0
230 -2.400494437457917 -0.8884888000889807 0
231 -2.434740206505228 -0.4429058760892239 0
232 -1.964720377034965 -1.293667708472078 0
233 -2.01933996144247 -0.8565871950067101 0
234 -2.068624514239478 -0.4275156525307307 0
235 -1.575416930632774 -1.233626257664419 0
236 -1.64166735878701 -0.8176813599726269 0
237 -1.697012105595388 -0.4086248381798738 0
238 -1.200429025084152 -1.161147932262043 0
239 -1.272506329260207 -0.7718009641075847 0
240 -1.327401030882088 -0.386422544456637 0
241 4.536348463925029 -1.481554877027547 0
242 4.076966575441157 -1.461826747345742 0
243 3.626632758299004 -1.439402719821685 0
244 3.190067289609639 -1.412951514178275 0
245 2.770240603008739 -1.381150766970896 0
246 2.363295268598255 -1.342158273522384 0
247 1.964720745715605 -1.29366873340107 0
248 1.575417171293567 -1.233626983666225 0
249 1.200429174776261 -1.161148389883658 0
250 4.530851405729067 -0.9863270776419871 0
251 4.066826269903716 -0.9717448686110475 0
252 3.614769893474459 -0.9552868751328539 0
253 3.183593918032332 -0.9362666689585752 0
254 2.783617050253483 -0.9144308326138045 0
255 2.400495065388643 -0.8884903509228984 0
256 2.019340386429411 -0.856588326996137 0
257 1.641667642233049 -0.8176821443214574 0
258 1.272506501026669 -0.771801400260968 0
259 4.518663549413066 -0.4925982624014307 0
260 4.04184693132522 -0.4846532618590358 0
261 3.577237250381986 -0.4756414769154902 0
262 3.139808324752386 -0.4653725616385991 0
263 2.777304908988505 -0.454902208732857 0
264 2.434740771273721 -0.4429073253123982 0
265 2.068624882856776 -0.4275166319116776 0
266 1.697012350109042 -0.4086254890422338 0
267 1.327401164359004 -0.3864228466778788 0
$EndNodes
$Elements
279
1 1 2 1 10 1 11
2 1 2 1 10 11 12
3 1 2 1 10 12 13
4 1 2 1 10 13 14
5 1 2 1 10 14 15
6 1 2 1 10 15 16
7 1 2 1 10 16 17
8 1 2 1 10 17 18
9 1 2 1 10 18 19
10 1 2 1 10 19 20
11 1 2 1 10 20 21
12 1 2 1 10 21 22
13 1 2 1 10 22 23
14 1 2 1 10 23 24
15 1 2 1 10 24 25
16 1 2 1 10 25 26
17 1 2 1 10 26 27
18 1 2 1 10 27 28
19 1 2 1 10 28 29
20 1 2 1 10 29 2
21 1 2 2 11 2 30
22 1 2 2 11 30 31
23 1 2 2 11 31 32
24 1 2 2 11 32 33
25 1 2 2 11 33 34
26 1 2 2 11 34 35
27 1 2 2 11 35 36
28 1 2 2 11 36 37
29 1 2 2 11 37 38
30 1 2 2 11 38 3
31 1 2 3 13 4 42
32 1 2 3 13 42 5
33 1 2 4 15 6 48
34 1 2 4 15 48 7
35 1 2 5 17 8 54
36 1 2 5 17 54 9
37 1 2 6 19 10 58
38 1 2 6 19 58 59
39 1 2 6 19 59 60
40 1 2 6 19 60 61
41 1 2 6 19 61 62
42 1 2 6 19 62 63
43 1 2 6 19 63 64
44 1 2 6 19 64 65
45 1 2 6 19 65 66
46 1 2 6 19 66 1
47 3 2 1 1 67 71 126 89
48 3 2 1 1 89 126 127 90
49 3 2 1 1 90 127 88 68
50 3 2 1 1 71 72 128 126
51 3 2 1 1 126 128 129 127
52 3 2 1 1 127 129 87 88
53 3 2 1 1 72 73 130 128
54 3 2 1 1 128 130 131 129
55 3 2 1 1 129 131 86 87
56 3 2 1 1 73 74 132 130
57 3 2 1 1 130 132 133 131
58 3 2 1 1 131 133 85 86
59 3 2 1 1 74 75 134 132
60 3 2 1 1 132 134 135 133
61 3 2 1 1 133 135 84 85
62 3 2 1 1 75 76 136 134
63 3 2 1 1 134 136 137 135
64 3 2 1 1 135 137 83 84
65 3 2 1 1 76 77 138 136
66 3 2 1 1 136 138 139 137
67 3 2 1 1 137 139 82 83
68 3 2 1 1 77 78 140 138
69 3 2 1 1 138 140 141 139
70 3 2 1 1 139 141 81 82
71 3 2 1 1 78 79 142 140
72 3 2 1 1 140 142 143 141
73 3 2 1 1 141 143 80 81
74 3 2 1 1 79 32 33 142
75 3 2 1 1 142 33 34 143
76 3 2 1 1 143 34 35 80
77 3 2 1 1 67 91 144 71
78 3 2 1 1 71 144 145 72
79 3 2 1 1 72 145 146 73
80 3 2 1 1 73 146 147 74
81 3 2 1 1 74 147 148 75
82 3 2 1 1 75 148 149 76
83 3 2 1 1 76 149 150 77
84 3 2 1 1 77 150 151 78
85 3 2 1 1 78 151 152 79
86 3 2 1 1 79 152 31 32
87 3 2 1 1 91 92 153 144
88 3 2 1 1 144 153 154 145
89 3 2 1 1 145 154 155 146
90 3 2 1 1 146 155 156 147
91 3 2 1 1 147 156 157 148
92 3 2 1 1 148 157 158 149
93 3 2 1 1 149 158 159 150
94 3 2 1 1 150 159 160 151
95 3 2 1 1 151 160 161 152
96 3 2 1 1 152 161 30 31
97 3 2 1 1 92 20 21 153
98 3 2 1 1 153 21 22 154
99 3 2 1 1 154 22 23 155
100 3 2 1 1 155 23 24 156
101 3 2 1 1 156 24 25 157
102 3 2 1 1 157 25 26 158
103 3 2 1 1 158 26 27 159
104 3 2 1 1 159 27 28 160
105 3 2 1 1 160 28 29 161
106 3 2 1 1 161 29 2 30
107 3 2 1 1 67 93 162 91
108 3 2 1 1 91 162 163 92
109 3 2 1 1 92 163 19 20
110 3 2 1 1 93 94 164 162
111 3 2 1 1 162 164 165 163
112 3 2 1 1 163 165 18 19
113 3 2 1 1 94 95 166 164
114 3 2 1 1 164 166 167 165
115 3 2 1 1 165 167 17 18
116 3 2 1 1 95 96 168 166
117 3 2 1 1 166 168 169 167
118 3 2 1 1 167 169 16 17
119 3 2 1 1 96 97 170 168
120 3 2 1 1 168 170 171 169
121 3 2 1 1 169 171 15 16
122 3 2 1 1 97 98 172 170
123 3 2 1 1 170 172 173 171
124 3 2 1 1 171 173 14 15
125 3 2 1 1 98 99 174 172
126 3 2 1 1 172 174 175 173
127 3 2 1 1 173 175 13 14
128 3 2 1 1 99 100 176 174
129 3 2 1 1 174 176 177 175
130 3 2 1 1 175 177 12 13
131 3 2 1 1 100 101 178 176
132 3 2 1 1 176 178 179 177
133 3 2 1 1 177 179 11 12
134 3 2 1 1 101 64 65 178
135 3 2 1 1 178 65 66 179
136 3 2 1 1 179 66 1 11
137 3 2 1 1 67 102 180 93
138 3 2 1 1 93 180 181 94
139 3 2 1 1 94 181 182 95
140 3 2 1 1 95 182 183 96
141 3 2 1 1 96 183 184 97
142 3 2 1 1 97 184 185 98
143 3 2 1 1 98 185 186 99
144 3 2 1 1 99 186 187 100
145 3 2 1 1 100 187 188 101
146 3 2 1 1 101 188 63 64
147 3 2 1 1 102 103 189 180
148 3 2 1 1 180 189 190 181
149 3 2 1 1 181 190 191 182
150 3 2 1 1 182 191 192 183
151 3 2 1 1 183 192 193 184
152 3 2 1 1 184 193 194 185
153 3 2 1 1 185 194 195 186
154 3 2 1 1 186 195 196 187
155 3 2 1 1 187 196 197 188
156 3 2 1 1 188 197 62 63
157 3 2 1 1 103 69 112 189
158 3 2 1 1 189 112 111 190
159 3 2 1 1 190 111 110 191
160 3 2 1 1 191 110 109 192
161 3 2 1 1 192 109 108 193
162 3 2 1 1 193 108 107 194
163 3 2 1 1 194 107 106 195
164 3 2 1 1 195 106 105 196
165 3 2 1 1 196 105 104 197
166 3 2 1 1 197 104 61 62
167 3 2 1 1 67 89 198 102
168 3 2 1 1 102 198 199 103
169 3 2 1 1 103 199 115 69
170 3 2 1 1 89 90 200 198
171 3 2 1 1 198 200 201 199
172 3 2 1 1 199 201 116 115
173 3 2 1 1 90 68 113 200
174 3 2 1 1 200 113 114 201
175 3 2 1 1 201 114 70 116
176 3 2 1 1 69 115 202 120
177 3 2 1 1 120 202 203 121
178 3 2 1 1 121 203 204 122
179 3 2 1 1 122 204 49 50
180 3 2 1 1 115 116 205 202
181 3 2 1 1 202 205 206 203
182 3 2 1 1 203 206 207 204
183 3 2 1 1 204 207 7 49
184 3 2 1 1 116 70 117 205
185 3 2 1 1 205 117 118 206
186 3 2 1 1 206 118 119 207
187 3 2 1 1 207 119 48 7
188 3 2 1 1 68 123 208 113
189 3 2 1 1 113 208 209 114
190 3 2 1 1 114 209 117 70
191 3 2 1 1 123 124 210 208
192 3 2 1 1 208 210 211 209
193 3 2 1 1 209 211 118 117
194 3 2 1 1 124 125 212 210
195 3 2 1 1 210 212 213 211
196 3 2 1 1 211 213 119 118
197 3 2 1 1 125 46 47 212
198 3 2 1 1 212 47 6 213
199 3 2 1 1 213 6 48 119
200 3 2 1 1 61 104 214 60
201 3 2 1 1 60 214 215 59
202 3 2 1 1 59 215 216 58
203 3 2 1 1 58 216 57 10
204 3 2 1 1 104 105 217 214
205 3 2 1 1 214 217 218 215
206 3 2 1 1 215 218 219 216
207 3 2 1 1 216 219 56 57
208 3 2 1 1 105 106 220 217
209 3 2 1 1 217 220 221 218
210 3 2 1 1 218 221 222 219
211 3 2 1 1 219 222 55 56
212 3 2 1 1 106 107 223 220
213 3 2 1 1 220 223 224 221
214 3 2 1 1 221 224 225 222
215 3 2 1 1 222 225 9 55
216 3 2 1 1 107 108 226 223
217 3 2 1 1 223 226 227 224
218 3 2 1 1 224 227 228 225
219 3 2 1 1 225 228 54 9
220 3 2 1 1 108 109 229 226
221 3 2 1 1 226 229 230 227
222 3 2 1 1 227 230 231 228
223 3 2 1 1 228 231 8 54
224 3 2 1 1 109 110 232 229
225 3 2 1 1 229 232 233 230
226 3 2 1 1 230 233 234 231
227 3 2 1 1 231 234 53 8
228 3 2 1 1 110 111 235 232
229 3 2 1 1 232 235 236 233
230 3 2 1 1 233 236 237 234
231 3 2 1 1 234 237 52 53
232 3 2 1 1 111 112 238 235
233 3 2 1 1 235 238 239 236
234 3 2 1 1 236 239 240 237
235 3 2 1 1 237 240 51 52
236 3 2 1 1 112 69 120 238
237 3 2 1 1 238 120 121 239
238 3 2 1 1 239 121 122 240
239 3 2 1 1 240 122 50 51
240 3 2 1 1 35 36 241 80
241 3 2 1 1 80 241 242 81
242 3 2 1 1 81 242 243 82
243 3 2 1 1 82 243 244 83
244 3 2 1 1 83 244 245 84
245 3 2 1 1 84 245 246 85
246 3 2 1 1 85 246 247 86
247 3 2 1 1 86 247 248 87
248 3 2 1 1 87 248 249 88
249 3 2 1 1 88 249 123 68
250 3 2 1 1 36 37 250 241
251 3 2 1 1 241 250 251 242
252 3 2 1 1 242 251 252 243
253 3 2 1 1 243 252 253 244
254 3 2 1 1 244 253 254 245
255 3 2 1 1 245 254 255 246
256 3 2 1 1 246 255 256 247
257 3 2 1 1 247 256 257 248
258 3 2 1 1 248 257 258 249
259 3 2 1 1 249 258 124 123
260 3 2 1 1 37 38 259 250
261 3 2 1 1 250 259 260 251
262 3 2 1 1 251 260 261 252
263 3 2 1 1 252 261 262 253
264 3 2 1 1 253 262 263 254
265 3 2 1 1 254 263 264 255
266 3 2 1 1 255 264 265 256
267 3 2 1 1 256 265 266 257
268 3 2 1 1 257 266 267 258
269 3 2 1 1 258 267 125 124
270 3 2 1 1 38 3 39 259
271 3 2 1 1 259 39 40 260
272 3 2 1 1 260 40 41 261
273 3 2 1 1 261 41 4 262
274 3 2 1 1 262 4 42 263
275 3 2 1 1 263 42 5 264
276 3 2 1 1 264 5 43 265
277 3 2 1 1 265 43 44 266
278 3 2 1 1 266 44 45 267
279 3 2 1 1 267 45 46 125
$EndElements
+841
View File
@@ -0,0 +1,841 @@
$MeshFormat
2.2 0 8
$EndMeshFormat
$Nodes
284
1 -5 -5 0
2 5 -5 0
3 5 0 0
4 3 0 0
5 2.5 0 0
6 0.25 0 0
7 -0.25 0 0
8 -2.5 0 0
9 -3 0 0
10 -5 0 0
11 -4.5 -5 0
12 -4 -5 0
13 -3.5 -5 0
14 -3 -5 0
15 -2.5 -5 0
16 -2 -5 0
17 -1.5 -5 0
18 -1 -5 0
19 -0.5 -5 0
20 0 -5 0
21 0.5 -5 0
22 1 -5 0
23 1.5 -5 0
24 2 -5 0
25 2.5 -5 0
26 3 -5 0
27 3.5 -5 0
28 4 -5 0
29 4.5 -5 0
30 5 -4.5 0
31 5 -4 0
32 5 -3.5 0
33 5 -3 0
34 5 -2.5 0
35 5 -2 0
36 5 -1.5 0
37 5 -1 0
38 5 -0.5 0
39 4.5 0 0
40 4 0 0
41 3.5 0 0
42 2.05 0 0
43 1.6 0 0
44 1.15 0 0
45 0.7 0 0
46 -0.7 0 0
47 -1.15 0 0
48 -1.6 0 0
49 -2.05 0 0
50 -3.5 0 0
51 -4 0 0
52 -4.5 0 0
53 -5 -0.5 0
54 -5 -1 0
55 -5 -1.5 0
56 -5 -2 0
57 -5 -2.5 0
58 -5 -3 0
59 -5 -3.5 0
60 -5 -4 0
61 -5 -4.5 0
62 2.281579424776069 -0.3939927528378067 0
63 0.9247461078594561 -0.4010584732281242 0
64 -2.251733651700584 -0.4058752540090222 0
65 -0.9249999999999999 -0.3860346567053154 0
66 -0.25 -4.566987298107781 0
67 -1.75 -4.566987298107781 0
68 1.757138123938968 -4.561388469215611 0
69 -4.57663533780044 -2.749996588117569 0
70 4.574218761575537 -2.744193863029555 0
71 -3.261047720525324 -4.633492158122827 0
72 3.233878916273093 -4.572861684713071 0
73 3.249983936371643 -0.4138167322607993 0
74 -3.22796127783195 -0.4745514342555692 0
75 0 -0.3897114317029975 0
76 0.7668085767464438 -4.554291553533576 0
77 4.524628320503154 -1.71259283597443 0
78 -4.57425038881721 -1.740509953296104 0
79 4.657280536213934 -3.745352555127771 0
80 -4.561557444591958 -3.743801811882471 0
81 4.247947141075545 -0.413931595411861 0
82 -4.25 -0.4330127018922192 0
83 -4.231971247749111 -4.526000871317382 0
84 4.247882011655604 -4.578053652047846 0
85 0.459240288050884 -0.4011062233317819 0
86 0.2191981222082625 -0.7894052414358377 0
87 -0.2550018029202653 -0.7696812455915785 0
88 -0.03974194597926101 -1.173522830121104 0
89 0.4346364211584371 -1.192775566160588 0
90 0.1864311373004761 -1.586413475031659 0
91 0.6507331103481842 -1.602794886574206 0
92 -0.2962063372658213 -1.560534711371811 0
93 0.4052226136760305 -1.998915829261101 0
94 0.8747101368415163 -2.014294995428312 0
95 0.6254442827334447 -2.414070730277764 0
96 1.096931360307092 -2.429431309425559 0
97 1.346300816143899 -2.027816295003789 0
98 1.569865887277439 -2.444122401305898 0
99 1.819907460974127 -2.038729916981179 0
100 2.044972612829793 -2.458848886499749 0
101 0.1536392683070031 -2.397215233401116 0
102 2.296563295216953 -2.050400407953151 0
103 2.523676371701205 -2.474026874448667 0
104 2.775089986948031 -2.064883565181495 0
105 0.3753978099944599 -2.816445321672421 0
106 -0.101594819363816 -2.799164055357317 0
107 1.321674174422592 -2.846267815879844 0
108 2.270294778623243 -2.882886829258818 0
109 2.752962034536527 -2.899399230379248 0
110 1.594627579321902 -1.620863532351973 0
111 2.496530594790508 -3.311620345179397 0
112 2.983571088917511 -3.327819126997308 0
113 0.1137225221607727 -3.227426914851572 0
114 -0.3711032418985809 -3.207832251592655 0
115 -0.5828344401467249 -2.781198932956502 0
116 -0.8353259116793657 -3.185936459987706 0
117 -1.059979017425433 -2.762083576904867 0
118 -1.314767961260485 -3.167209797744215 0
119 -1.538368860989399 -2.742671230707596 0
120 -1.793679531768132 -3.151042894839176 0
121 -2.01924352863926 -2.723529808724752 0
122 -1.762667957890364 -2.316647156105704 0
123 -2.242107472177446 -2.296152128033975 0
124 -2.502564281105353 -2.703893235067956 0
125 -2.725331546445376 -2.27365855104896 0
126 -2.987889362306672 -2.683287954718313 0
127 -1.983692675350436 -1.892945890847701 0
128 -2.763007610522606 -3.115691622644485 0
129 -3.251879284051252 -3.095617007538864 0
130 -3.21286057170471 -2.250678390947021 0
131 -2.949339074017358 -1.839974815980651 0
132 3.239407644843084 -2.914212985735978 0
133 2.547241608329427 -1.641063282475632 0
134 3.028142281477624 -1.652825101445818 0
135 -1.510406177790435 -1.913844107670896 0
136 -1.730236214842076 -1.505967151038945 0
137 -1.091336732540506 -3.593626264859387 0
138 -3.421854185011473 -1.808744480608429 0
139 3.472898669730996 -3.34330809310189 0
140 -1.256990758306386 -1.517443982643851 0
141 -3.024894304094996 -3.530456033168236 0
142 -3.516854524302131 -3.510343830944479 0
143 -1.03266707250538 -1.934584786094152 0
144 -2.201596179944664 -1.480657719683199 0
145 0.9021516016281497 -1.202776512596341 0
146 2.725588881466229 -3.738813882257535 0
147 -2.53516560562837 -3.550136689533193 0
148 -1.44581323125898 -1.123897963936046 0
149 2.235681770010328 -3.726033843511675 0
150 3.259277130723387 -2.077185219746537 0
151 3.513246676438002 -1.662021998760507 0
152 3.758409946183188 -2.082693761823018 0
153 0.5991111801411881 -3.240104427542418 0
154 0.3369992297204003 -3.654513295976306 0
155 0.8290255688225511 -3.666471451892976 0
156 -3.722265329818222 -2.240608173907041 0
157 -3.171684897998753 -1.403346357255416 0
158 -3.644197255924033 -1.372619186245074 0
159 2.794592285631932 -1.233756575634245 0
160 2.318227283889231 -1.220216315858479 0
161 -0.9783790307780148 -1.130197912479716 0
162 -3.713876128115184 -3.079033658054799 0
163 3.742324371372884 -2.928999279490477 0
164 2.485083477357409 -4.14729896608471 0
165 -2.778145642709027 -3.956644572916525 0
166 -2.304612039225867 -3.9863489431438 0
167 3.966968926084867 -3.336303597578833 0
168 3.706083502027171 -3.750175369113853 0
169 -4.077410641740634 -3.501439180006514 0
170 -3.76933168214444 -3.914132682209269 0
171 -2.568167113352794 -4.403628334893856 0
172 3.999000211194053 -1.669214607229196 0
173 3.757262029468758 -1.246079492069315 0
174 4.257624135642195 -1.243021671724434 0
175 -3.406533611304276 -0.962718897978591 0
176 -2.86976350923752 -0.9741763965199361 0
177 -3.887783979793185 -0.9468337725874192 0
178 -4.151590072554236 -1.433655682692203 0
179 2.567375777521253 -0.8162946823393982 0
180 -2.710501565840532 -0.449194289380908 0
181 -2.361189298792369 -0.8166861741006153 0
182 -1.992682834578591 -0.6868709027668873 0
183 0.5758835407852954 -4.076507624142248 0
184 1.063684706252736 -4.093379787905304 0
185 1.307846713632664 -3.678677225448682 0
186 0.06794786130557443 -4.080395137507568 0
187 -2.678398828890455 -1.460184324491551 0
188 -0.8061658045868053 -2.358239065659463 0
189 -4.417190667034111 -0.9530981038866508 0
190 -3.73616854160164 -0.4667243592037824 0
191 2.068162362263069 -1.631550568057341 0
192 1.835382136049844 -1.217564469998849 0
193 2.076433566739975 -0.8129459415658435 0
194 1.61110337936101 -0.8034909695554286 0
195 1.370865405519029 -1.205639238805478 0
196 1.266721313972774 -4.554232785919753 0
197 1.152132432870027 -0.7985062781329273 0
198 3.748778721192141 -0.4141047359259749 0
199 -0.3239021359339606 -2.378890209329921 0
200 0.2607890790366596 -4.560067219429725 0
201 -0.4362762851344742 -4.076426953781812 0
202 -0.7421904022893758 -4.524993181824712 0
203 -1.253691857241304 -4.549910310088686 0
204 -1.488540618385916 -4.054529834302866 0
205 1.541536749303611 -4.112783475173385 0
206 -0.5479417186259372 -1.958200565306824 0
207 -0.7713233681764451 -1.536925116417346 0
208 -0.5067754141866345 -1.151982463020497 0
209 -2.463410962804289 -1.87392890501434 0
210 -1.285472530570352 -2.338305660158975 0
211 1.378798590411674 -0.3978505940829993 0
212 4.568566441173747 -2.253723730329266 0
213 4.088512427247694 -2.504191622867672 0
214 -4.571120642477398 -2.248470981336947 0
215 -4.173258424046725 -2.492769868254391 0
216 -2.047412941544885 -3.538687166079191 0
217 1.125824650205117 -1.614348493757459 0
218 -4.567172963194309 -3.249678418636941 0
219 4.501762177208068 -3.291802327374498 0
220 2.734404034710506 -4.574544109781256 0
221 3.737632412102343 -4.577017677939905 0
222 -3.747757947362715 -4.608888775351113 0
223 0.6858343131338018 -0.7970681284062564 0
224 2.015439853666996 -3.2910097832621 0
225 -1.370883691544074 -0.3644617263994698 0
226 -0.4732994730374375 -0.3853059645196071 0
227 -4.170675295358781 -1.983559227818775 0
228 -2.277544785346445 -3.130935475978483 0
229 -3.263718927452331 -3.912011456955563 0
230 -0.1542685254652809 -3.640226549090094 0
231 -3.454561268417778 -2.67774506671365 0
232 3.214928094260674 -3.743707595789564 0
233 3.007108075975173 -2.488409339011342 0
234 2.750375637050162 -0.4010587521556839 0
235 3.269904368436389 -1.242599673877227 0
236 0.8478171295042514 -2.831115790911858 0
237 -0.0704595287570349 -1.980028337283739 0
238 1.793648233980072 -2.86032311009413 0
239 3.515839932724234 -2.499282034779171 0
240 1.07506868241603 -3.253368514666169 0
241 -1.567508338488392 -3.57138356920831 0
242 -0.9414895224228255 -4.065708547614685 0
243 1.832985826881454 -0.4013800430070131 0
244 1.547665348273904 -3.266345147264329 0
245 2.24775968641297 -4.567837723912959 0
246 3.024151650683252 -0.8281976201130985 0
247 3.975810147196631 -4.172641805166976 0
248 3.503728254202536 -0.8279311588352849 0
249 4.019169941690009 -0.8158447756611453 0
250 -4.60633771632638 -4.233723530566535 0
251 4.538799499276811 -4.205405374535824 0
252 4.562305311857926 -0.7843140484770377 0
253 2.972986107264538 -4.155124335587413 0
254 1.778207995839603 -3.692155396798268 0
255 -0.6383000365235054 -3.628292837821056 0
256 2.007567967143815 -4.134582979116102 0
257 3.473553196520742 -4.16192141138513 0
258 -0.7147426201537252 -0.7593778023334516 0
259 -1.806767054514615 -0.3633765595208169 0
260 -4.167097947097267 -2.975013458090675 0
261 4.175407739112876 -2.963681551184706 0
262 -1.168353453833206 -0.7478369226339197 0
263 -4.628606225681111 -1.325452747974991 0
264 -1.955754170223746 -1.109359237158393 0
265 -2.181031249640995 -4.584142759217013 0
266 4.182701247812142 -2.054804320229848 0
267 -4.210523009401651 -3.998563903983795 0
268 4.199775238004059 -3.750658239585041 0
269 -1.916827765372219 -4.194625820537434 0
270 -1.592821679170089 -0.7265436064032613 0
271 4.67022823811257 -1.25 0
272 -3.831566914438898 -1.745340707425962 0
273 -4.63781179938241 -0.6091765096601387 0
274 -3.039941547144599 -4.302681803322779 0
275 -3.505011562055658 -4.271356754644082 0
276 -4.633974596215562 -4.633974596215562 0
277 4.633974596215562 -4.633974596215562 0
278 4.636520367722471 -0.3634796322775284 0
279 -2.418900170891447 -1.187862709206192 0
280 -2.873831276204544 -4.667960459267892 0
281 -3.842482079912878 -2.704892577303744 0
282 -3.908734077731807 -4.261830041615932 0
283 -1.821328914771955 -3.849262003031382 0
284 -4.705859493625653 -0.2869977523880989 0
$EndNodes
$Elements
548
1 1 2 1 10 1 11
2 1 2 1 10 11 12
3 1 2 1 10 12 13
4 1 2 1 10 13 14
5 1 2 1 10 14 15
6 1 2 1 10 15 16
7 1 2 1 10 16 17
8 1 2 1 10 17 18
9 1 2 1 10 18 19
10 1 2 1 10 19 20
11 1 2 1 10 20 21
12 1 2 1 10 21 22
13 1 2 1 10 22 23
14 1 2 1 10 23 24
15 1 2 1 10 24 25
16 1 2 1 10 25 26
17 1 2 1 10 26 27
18 1 2 1 10 27 28
19 1 2 1 10 28 29
20 1 2 1 10 29 2
21 1 2 2 11 2 30
22 1 2 2 11 30 31
23 1 2 2 11 31 32
24 1 2 2 11 32 33
25 1 2 2 11 33 34
26 1 2 2 11 34 35
27 1 2 2 11 35 36
28 1 2 2 11 36 37
29 1 2 2 11 37 38
30 1 2 2 11 38 3
31 1 2 3 13 4 5
32 1 2 4 15 6 7
33 1 2 5 17 8 9
34 1 2 6 19 10 53
35 1 2 6 19 53 54
36 1 2 6 19 54 55
37 1 2 6 19 55 56
38 1 2 6 19 56 57
39 1 2 6 19 57 58
40 1 2 6 19 58 59
41 1 2 6 19 59 60
42 1 2 6 19 60 61
43 1 2 6 19 61 1
44 2 2 1 1 137 241 204
45 2 2 1 1 204 242 137
46 2 2 1 1 175 176 74
47 2 2 1 1 176 180 74
48 2 2 1 1 201 255 242
49 2 2 1 1 167 268 219
50 2 2 1 1 213 239 163
51 2 2 1 1 9 180 8
52 2 2 1 1 152 239 213
53 2 2 1 1 169 218 80
54 2 2 1 1 78 227 178
55 2 2 1 1 174 252 249
56 2 2 1 1 74 190 175
57 2 2 1 1 251 268 247
58 2 2 1 1 219 268 79
59 2 2 1 1 186 201 66
60 2 2 1 1 8 180 64
61 2 2 1 1 79 268 251
62 2 2 1 1 66 200 186
63 2 2 1 1 177 190 82
64 2 2 1 1 19 202 18
65 2 2 1 1 77 174 172
66 2 2 1 1 82 189 177
67 2 2 1 1 227 272 178
68 2 2 1 1 175 190 177
69 2 2 1 1 265 269 166
70 2 2 1 1 242 255 137
71 2 2 1 1 171 265 166
72 2 2 1 1 186 200 183
73 2 2 1 1 189 273 54
74 2 2 1 1 80 267 169
75 2 2 1 1 183 200 76
76 2 2 1 1 54 263 189
77 2 2 1 1 74 180 9
78 2 2 1 1 88 208 92
79 2 2 1 1 202 203 18
80 2 2 1 1 169 260 218
81 2 2 1 1 201 242 202
82 2 2 1 1 219 261 167
83 2 2 1 1 249 252 81
84 2 2 1 1 76 184 183
85 2 2 1 1 62 193 179
86 2 2 1 1 113 230 154
87 2 2 1 1 148 270 264
88 2 2 1 1 66 202 19
89 2 2 1 1 92 208 207
90 2 2 1 1 174 271 252
91 2 2 1 1 76 196 184
92 2 2 1 1 15 265 171
93 2 2 1 1 264 270 182
94 2 2 1 1 229 275 274
95 2 2 1 1 181 264 182
96 2 2 1 1 176 181 180
97 2 2 1 1 7 75 6
98 2 2 1 1 234 246 73
99 2 2 1 1 87 208 88
100 2 2 1 1 274 275 71
101 2 2 1 1 193 243 194
102 2 2 1 1 84 251 247
103 2 2 1 1 77 271 174
104 2 2 1 1 171 280 15
105 2 2 1 1 51 190 50
106 2 2 1 1 163 261 213
107 2 2 1 1 247 257 221
108 2 2 1 1 68 205 196
109 2 2 1 1 180 181 64
110 2 2 1 1 114 230 113
111 2 2 1 1 196 205 184
112 2 2 1 1 181 279 264
113 2 2 1 1 50 190 74
114 2 2 1 1 154 230 186
115 2 2 1 1 172 266 77
116 2 2 1 1 92 207 206
117 2 2 1 1 62 243 193
118 2 2 1 1 201 202 66
119 2 2 1 1 89 223 86
120 2 2 1 1 195 197 145
121 2 2 1 1 140 207 161
122 2 2 1 1 188 206 143
123 2 2 1 1 179 193 160
124 2 2 1 1 63 211 44
125 2 2 1 1 202 242 203
126 2 2 1 1 45 85 63
127 2 2 1 1 206 207 143
128 2 2 1 1 160 193 192
129 2 2 1 1 4 234 73
130 2 2 1 1 82 190 51
131 2 2 1 1 203 242 204
132 2 2 1 1 179 234 62
133 2 2 1 1 131 209 187
134 2 2 1 1 187 209 144
135 2 2 1 1 176 279 181
136 2 2 1 1 145 217 195
137 2 2 1 1 143 207 140
138 2 2 1 1 7 226 75
139 2 2 1 1 179 246 234
140 2 2 1 1 44 211 43
141 2 2 1 1 199 237 206
142 2 2 1 1 143 210 188
143 2 2 1 1 85 223 63
144 2 2 1 1 160 192 191
145 2 2 1 1 186 230 201
146 2 2 1 1 195 217 110
147 2 2 1 1 17 203 67
148 2 2 1 1 197 211 63
149 2 2 1 1 6 85 45
150 2 2 1 1 220 245 25
151 2 2 1 1 86 223 85
152 2 2 1 1 99 191 110
153 2 2 1 1 197 223 145
154 2 2 1 1 207 208 161
155 2 2 1 1 206 237 92
156 2 2 1 1 18 203 17
157 2 2 1 1 63 223 197
158 2 2 1 1 191 192 110
159 2 2 1 1 102 191 99
160 2 2 1 1 203 204 67
161 2 2 1 1 156 227 215
162 2 2 1 1 181 182 64
163 2 2 1 1 23 196 22
164 2 2 1 1 145 223 89
165 2 2 1 1 192 195 110
166 2 2 1 1 83 267 250
167 2 2 1 1 168 257 247
168 2 2 1 1 75 226 87
169 2 2 1 1 193 194 192
170 2 2 1 1 22 196 76
171 2 2 1 1 68 196 23
172 2 2 1 1 75 85 6
173 2 2 1 1 159 160 133
174 2 2 1 1 245 256 68
175 2 2 1 1 188 210 117
176 2 2 1 1 125 209 131
177 2 2 1 1 157 176 175
178 2 2 1 1 4 73 41
179 2 2 1 1 50 74 9
180 2 2 1 1 164 245 220
181 2 2 1 1 159 179 160
182 2 2 1 1 216 228 147
183 2 2 1 1 194 243 211
184 2 2 1 1 94 217 91
185 2 2 1 1 40 198 81
186 2 2 1 1 157 187 176
187 2 2 1 1 122 210 135
188 2 2 1 1 91 217 145
189 2 2 1 1 135 210 143
190 2 2 1 1 120 228 216
191 2 2 1 1 101 199 106
192 2 2 1 1 122 135 127
193 2 2 1 1 133 191 102
194 2 2 1 1 135 136 127
195 2 2 1 1 160 191 133
196 2 2 1 1 73 198 41
197 2 2 1 1 140 148 136
198 2 2 1 1 101 237 199
199 2 2 1 1 20 200 66
200 2 2 1 1 135 140 136
201 2 2 1 1 178 263 78
202 2 2 1 1 140 161 148
203 2 2 1 1 122 127 123
204 2 2 1 1 115 188 117
205 2 2 1 1 106 199 115
206 2 2 1 1 131 187 157
207 2 2 1 1 99 110 97
208 2 2 1 1 199 206 188
209 2 2 1 1 5 234 4
210 2 2 1 1 119 122 121
211 2 2 1 1 115 199 188
212 2 2 1 1 230 255 201
213 2 2 1 1 76 200 21
214 2 2 1 1 97 217 94
215 2 2 1 1 169 267 170
216 2 2 1 1 104 133 102
217 2 2 1 1 56 214 78
218 2 2 1 1 77 212 35
219 2 2 1 1 119 210 122
220 2 2 1 1 212 213 70
221 2 2 1 1 17 67 16
222 2 2 1 1 20 66 19
223 2 2 1 1 22 76 21
224 2 2 1 1 24 68 23
225 2 2 1 1 69 215 214
226 2 2 1 1 117 210 119
227 2 2 1 1 194 197 195
228 2 2 1 1 194 211 197
229 2 2 1 1 215 227 214
230 2 2 1 1 136 144 127
231 2 2 1 1 110 217 97
232 2 2 1 1 21 200 20
233 2 2 1 1 36 77 35
234 2 2 1 1 56 78 55
235 2 2 1 1 115 117 116
236 2 2 1 1 194 195 192
237 2 2 1 1 41 198 40
238 2 2 1 1 134 235 159
239 2 2 1 1 34 212 70
240 2 2 1 1 69 214 57
241 2 2 1 1 208 258 161
242 2 2 1 1 213 266 152
243 2 2 1 1 106 115 114
244 2 2 1 1 35 212 34
245 2 2 1 1 57 214 56
246 2 2 1 1 135 143 140
247 2 2 1 1 123 209 125
248 2 2 1 1 134 159 133
249 2 2 1 1 26 220 25
250 2 2 1 1 32 219 79
251 2 2 1 1 80 218 59
252 2 2 1 1 34 70 33
253 2 2 1 1 58 69 57
254 2 2 1 1 58 218 69
255 2 2 1 1 70 219 33
256 2 2 1 1 131 157 138
257 2 2 1 1 147 228 128
258 2 2 1 1 91 145 89
259 2 2 1 1 27 221 72
260 2 2 1 1 40 81 39
261 2 2 1 1 52 82 51
262 2 2 1 1 12 222 83
263 2 2 1 1 14 71 13
264 2 2 1 1 27 72 26
265 2 2 1 1 71 222 13
266 2 2 1 1 84 221 28
267 2 2 1 1 28 221 27
268 2 2 1 1 33 219 32
269 2 2 1 1 59 218 58
270 2 2 1 1 32 79 31
271 2 2 1 1 60 80 59
272 2 2 1 1 13 222 12
273 2 2 1 1 12 83 11
274 2 2 1 1 29 84 28
275 2 2 1 1 125 131 130
276 2 2 1 1 96 97 94
277 2 2 1 1 166 216 147
278 2 2 1 1 117 119 118
279 2 2 1 1 184 185 155
280 2 2 1 1 72 220 26
281 2 2 1 1 157 175 158
282 2 2 1 1 141 147 128
283 2 2 1 1 122 123 121
284 2 2 1 1 214 227 78
285 2 2 1 1 165 166 147
286 2 2 1 1 115 116 114
287 2 2 1 1 117 118 116
288 2 2 1 1 131 138 130
289 2 2 1 1 98 99 97
290 2 2 1 1 151 235 134
291 2 2 1 1 241 283 204
292 2 2 1 1 104 134 133
293 2 2 1 1 111 149 146
294 2 2 1 1 75 86 85
295 2 2 1 1 108 224 111
296 2 2 1 1 169 170 142
297 2 2 1 1 172 173 151
298 2 2 1 1 111 224 149
299 2 2 1 1 119 121 120
300 2 2 1 1 141 165 147
301 2 2 1 1 54 273 53
302 2 2 1 1 129 162 142
303 2 2 1 1 184 205 185
304 2 2 1 1 96 98 97
305 2 2 1 1 112 132 109
306 2 2 1 1 127 209 123
307 2 2 1 1 42 62 5
308 2 2 1 1 8 64 49
309 2 2 1 1 45 63 44
310 2 2 1 1 47 65 46
311 2 2 1 1 46 226 7
312 2 2 1 1 157 158 138
313 2 2 1 1 48 225 47
314 2 2 1 1 165 171 166
315 2 2 1 1 126 128 124
316 2 2 1 1 162 169 142
317 2 2 1 1 125 130 126
318 2 2 1 1 75 87 86
319 2 2 1 1 139 163 132
320 2 2 1 1 119 120 118
321 2 2 1 1 149 164 146
322 2 2 1 1 139 168 167
323 2 2 1 1 129 141 128
324 2 2 1 1 138 156 130
325 2 2 1 1 123 125 124
326 2 2 1 1 108 238 224
327 2 2 1 1 129 142 141
328 2 2 1 1 161 262 148
329 2 2 1 1 68 256 205
330 2 2 1 1 126 129 128
331 2 2 1 1 172 174 173
332 2 2 1 1 139 167 163
333 2 2 1 1 125 126 124
334 2 2 1 1 112 139 132
335 2 2 1 1 175 177 158
336 2 2 1 1 236 240 107
337 2 2 1 1 93 101 95
338 2 2 1 1 154 186 183
339 2 2 1 1 101 106 105
340 2 2 1 1 177 189 178
341 2 2 1 1 106 114 113
342 2 2 1 1 88 92 90
343 2 2 1 1 152 172 151
344 2 2 1 1 170 229 142
345 2 2 1 1 88 89 86
346 2 2 1 1 183 184 155
347 2 2 1 1 90 91 89
348 2 2 1 1 154 155 153
349 2 2 1 1 113 153 105
350 2 2 1 1 103 104 102
351 2 2 1 1 100 108 103
352 2 2 1 1 111 146 112
353 2 2 1 1 100 102 99
354 2 2 1 1 98 100 99
355 2 2 1 1 177 178 158
356 2 2 1 1 93 94 91
357 2 2 1 1 111 112 109
358 2 2 1 1 95 96 94
359 2 2 1 1 150 152 151
360 2 2 1 1 150 151 134
361 2 2 1 1 118 137 116
362 2 2 1 1 121 228 120
363 2 2 1 1 108 111 109
364 2 2 1 1 185 240 155
365 2 2 1 1 96 107 98
366 2 2 1 1 129 231 162
367 2 2 1 1 141 229 165
368 2 2 1 1 128 228 124
369 2 2 1 1 153 240 236
370 2 2 1 1 123 124 121
371 2 2 1 1 108 109 103
372 2 2 1 1 142 229 141
373 2 2 1 1 87 88 86
374 2 2 1 1 100 103 102
375 2 2 1 1 106 113 105
376 2 2 1 1 101 105 95
377 2 2 1 1 154 183 155
378 2 2 1 1 65 226 46
379 2 2 1 1 88 90 89
380 2 2 1 1 90 93 91
381 2 2 1 1 113 154 153
382 2 2 1 1 156 231 130
383 2 2 1 1 130 231 126
384 2 2 1 1 124 228 121
385 2 2 1 1 93 95 94
386 2 2 1 1 47 225 65
387 2 2 1 1 126 231 129
388 2 2 1 1 132 233 109
389 2 2 1 1 144 209 127
390 2 2 1 1 173 235 151
391 2 2 1 1 112 232 139
392 2 2 1 1 139 232 168
393 2 2 1 1 104 150 134
394 2 2 1 1 146 232 112
395 2 2 1 1 103 233 104
396 2 2 1 1 211 243 43
397 2 2 1 1 109 233 103
398 2 2 1 1 104 233 150
399 2 2 1 1 132 239 233
400 2 2 1 1 93 237 101
401 2 2 1 1 92 237 90
402 2 2 1 1 233 239 150
403 2 2 1 1 96 236 107
404 2 2 1 1 153 236 105
405 2 2 1 1 95 236 96
406 2 2 1 1 216 241 120
407 2 2 1 1 163 239 132
408 2 2 1 1 72 253 220
409 2 2 1 1 162 260 169
410 2 2 1 1 43 243 42
411 2 2 1 1 105 236 95
412 2 2 1 1 90 237 93
413 2 2 1 1 100 238 108
414 2 2 1 1 98 238 100
415 2 2 1 1 25 245 24
416 2 2 1 1 107 238 98
417 2 2 1 1 187 279 176
418 2 2 1 1 258 262 161
419 2 2 1 1 120 241 118
420 2 2 1 1 83 282 267
421 2 2 1 1 150 239 152
422 2 2 1 1 173 249 248
423 2 2 1 1 155 240 153
424 2 2 1 1 118 241 137
425 2 2 1 1 221 257 72
426 2 2 1 1 31 251 30
427 2 2 1 1 61 250 60
428 2 2 1 1 174 249 173
429 2 2 1 1 173 248 235
430 2 2 1 1 62 234 5
431 2 2 1 1 185 244 240
432 2 2 1 1 235 246 159
433 2 2 1 1 220 253 164
434 2 2 1 1 42 243 62
435 2 2 1 1 24 245 68
436 2 2 1 1 164 253 146
437 2 2 1 1 238 244 224
438 2 2 1 1 87 258 208
439 2 2 1 1 114 255 230
440 2 2 1 1 148 264 136
441 2 2 1 1 69 260 215
442 2 2 1 1 116 255 114
443 2 2 1 1 72 257 253
444 2 2 1 1 185 254 244
445 2 2 1 1 198 249 81
446 2 2 1 1 107 244 238
447 2 2 1 1 159 246 179
448 2 2 1 1 84 247 221
449 2 2 1 1 240 244 107
450 2 2 1 1 164 256 245
451 2 2 1 1 182 259 64
452 2 2 1 1 73 248 198
453 2 2 1 1 235 248 246
454 2 2 1 1 226 258 87
455 2 2 1 1 248 249 198
456 2 2 1 1 244 254 224
457 2 2 1 1 55 263 54
458 2 2 1 1 64 259 49
459 2 2 1 1 205 254 185
460 2 2 1 1 48 259 225
461 2 2 1 1 60 250 80
462 2 2 1 1 79 251 31
463 2 2 1 1 205 256 254
464 2 2 1 1 16 265 15
465 2 2 1 1 77 266 212
466 2 2 1 1 38 252 37
467 2 2 1 1 262 270 148
468 2 2 1 1 36 271 77
469 2 2 1 1 146 253 232
470 2 2 1 1 246 248 73
471 2 2 1 1 70 261 219
472 2 2 1 1 224 254 149
473 2 2 1 1 137 255 116
474 2 2 1 1 254 256 149
475 2 2 1 1 215 281 156
476 2 2 1 1 166 283 216
477 2 2 1 1 11 276 1
478 2 2 1 1 1 276 61
479 2 2 1 1 2 277 29
480 2 2 1 1 30 277 2
481 2 2 1 1 259 270 225
482 2 2 1 1 156 272 227
483 2 2 1 1 149 256 164
484 2 2 1 1 138 272 156
485 2 2 1 1 178 272 158
486 2 2 1 1 3 278 38
487 2 2 1 1 39 278 3
488 2 2 1 1 213 261 70
489 2 2 1 1 229 274 165
490 2 2 1 1 253 257 232
491 2 2 1 1 232 257 168
492 2 2 1 1 49 259 48
493 2 2 1 1 65 258 226
494 2 2 1 1 218 260 69
495 2 2 1 1 156 281 231
496 2 2 1 1 52 284 82
497 2 2 1 1 252 271 37
498 2 2 1 1 167 261 163
499 2 2 1 1 65 262 258
500 2 2 1 1 225 262 65
501 2 2 1 1 189 263 178
502 2 2 1 1 78 263 55
503 2 2 1 1 136 264 144
504 2 2 1 1 212 266 213
505 2 2 1 1 67 265 16
506 2 2 1 1 269 283 166
507 2 2 1 1 182 270 259
508 2 2 1 1 204 269 67
509 2 2 1 1 152 266 172
510 2 2 1 1 67 269 265
511 2 2 1 1 168 268 167
512 2 2 1 1 260 281 215
513 2 2 1 1 15 280 14
514 2 2 1 1 71 275 222
515 2 2 1 1 247 268 168
516 2 2 1 1 264 279 144
517 2 2 1 1 37 271 36
518 2 2 1 1 250 267 80
519 2 2 1 1 144 279 187
520 2 2 1 1 82 273 189
521 2 2 1 1 225 270 262
522 2 2 1 1 14 280 71
523 2 2 1 1 82 284 273
524 2 2 1 1 222 282 83
525 2 2 1 1 158 272 138
526 2 2 1 1 251 277 30
527 2 2 1 1 61 276 250
528 2 2 1 1 81 278 39
529 2 2 1 1 83 276 11
530 2 2 1 1 29 277 84
531 2 2 1 1 165 274 171
532 2 2 1 1 170 275 229
533 2 2 1 1 53 284 10
534 2 2 1 1 10 284 52
535 2 2 1 1 231 281 162
536 2 2 1 1 162 281 260
537 2 2 1 1 250 276 83
538 2 2 1 1 84 277 251
539 2 2 1 1 216 283 241
540 2 2 1 1 252 278 81
541 2 2 1 1 38 278 252
542 2 2 1 1 204 283 269
543 2 2 1 1 71 280 274
544 2 2 1 1 267 282 170
545 2 2 1 1 170 282 275
546 2 2 1 1 275 282 222
547 2 2 1 1 274 280 171
548 2 2 1 1 273 284 53
$EndElements
@@ -0,0 +1,817 @@
#include "linear_elasticity.hpp"
using namespace mfem;
using mfem::future::dual;
using mfem::future::tuple;
using mfem::future::tensor;
using mfem::future::Weight;
using mfem::future::Gradient;
using mfem::future::Identity;
LinearElasticityTimeDependentOperator::LinearElasticityTimeDependentOperator(ParMesh &mesh_, int vorder)
: TimeDependentOperator(),
mesh(mesh_),
order(vorder)
{
mesh.EnsureNodes();
dim = mesh.Dimension();
space_dim = mesh.SpaceDimension();
fec = std::make_unique<H1_FECollection>(order, dim);
fespace = std::make_unique<ParFiniteElementSpace>(&mesh, fec.get(), dim, Ordering::byNODES);
nodes = static_cast<ParGridFunction *>(mesh.GetNodes());
mfes = nodes->ParFESpace();
domain_attributes.SetSize(mesh_.attributes.Max());
const mfem::FiniteElement *fe= fespace->GetFE(0);
ir = &(IntRules.Get(fe->GetGeomType(),
fe->GetOrder() + fe->GetOrder() + fe->GetDim() - 1));
qs.reset(new QuadratureSpace(mesh, *ir));
fqs.reset(new FaceQuadratureSpace(mesh, order+1, FaceType::Boundary));
ups.reset(new future::UniformParameterSpace(
mesh, *ir, 1, false /* used_in_tensor_product */));
if (mesh.attributes.Size() > 0)
{
domain_attributes.SetSize(mesh.attributes.Max());
domain_attributes = 1;
}
block_true_offsets.SetSize(3);
block_true_offsets[0] = 0;
block_true_offsets[1] = fespace->TrueVSize();
block_true_offsets[2] = fespace->TrueVSize();
block_true_offsets.PartialSum();
sol.Update(block_true_offsets); sol=0.0; sol.UseDevice(true);
rhs.Update(block_true_offsets); rhs=0.0; rhs.UseDevice(true);
tmp.Update(block_true_offsets); tmp=0.0; tmp.UseDevice(true);
res.SetSize(fespace->GetTrueVSize()); res=0.0; res.UseDevice(true);
displ.SetSpace(fespace.get()); displ=0.0;
displ.SetTrueVector();
displ.GetTrueVector().UseDevice(true);
veloc.SetSpace(fespace.get()); veloc=0.0;
veloc.SetTrueVector();
veloc.GetTrueVector().UseDevice(true);
this->width = 2*fespace->TrueVSize();
this->height = 2*fespace->TrueVSize();
MPI_Comm_rank(mesh.GetComm(),&myrank);
vol_force_mem.SetSize(10);
vol_force_mem.UseDevice(true);
vol_force_mem(0) = 0.0; // time
vol_force_mem(1) = 1.0; // period
vol_force_mem(2) = 0.0; // amplitude
vol_force_mem(3) = 0.5; // radius
vol_force_mem(4) = 0.0; // x coordinate of the center
vol_force_mem(5) = 0.0; // y coordinate of the center
vol_force_mem(6) = 0.0; // z coordinate of the center
vol_force_mem(7) = 5*vol_force_mem(1); // total train length
vol_force_mem(8) = vol_force_mem(7)/2.0;
vol_force_mem(9) = 2.0;
bdr_force_mem.SetSize(3);
bdr_force_mem.UseDevice(true);
bdr_force_mem(0) = 0.0; // time
bdr_force_mem(1) = 1.0; // period
bdr_force_mem(2) = 0.0; // amplitude
}
template <int DI, typename scalar_t=real_t> struct QElasticityFunction
{
using matd_t = tensor<scalar_t, DI, DI>;
using vecd_t = tensor<scalar_t, DI>;
using vec_t = tensor<real_t, DI>;
using mat_t = tensor<real_t, DI, DI>;
struct Mass
{
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const real_t &dens1,
const real_t &dens2,
const scalar_t &density,
const matd_t &J,
const real_t &w) const
{
const auto dens = density*dens2 + (1.0-density)*dens1;
const auto detJ = mfem::future::det(J);
return tuple{dens * u * detJ * w};
}
};
struct Elasticity
{
MFEM_HOST_DEVICE inline auto operator()(const matd_t &dudxi,
const real_t &L1,
const real_t &M1,
const real_t &L2,
const real_t &M2,
const scalar_t &density,
const matd_t &J,
const real_t &w) const
{
const matd_t JxW = transpose(inv(J)) * det(J) * w;
constexpr auto I = mfem::future::IsotropicIdentity<DI>();
const auto eps = mfem::future::sym(dudxi * mfem::future::inv(J));
const auto L = density*L2 + (1.0-density)*L1;
const auto M = density*M2 + (1.0-density)*M1;
return tuple{(L * tr(eps) * I + 2.0 * M * eps) * JxW};
}
};
struct DynamicBdrForce
{
//real_t time=0.0;
//real_t period=1.0;
mfem::Vector* time_mem;
//mfem::Memory<int> alt_time; check the documentation about Memory class for more details
DynamicBdrForce(mfem::Vector& tm) // the Read method should be called on the vector passed as tm
// before calling the Mult on the differentiable operator when
// the time is changing, i.e., the values between the host
// and device have to be synchronized.
{
time_mem = tm.Read(); //get the device pointer
}
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const matd_t &J,
const real_t &w
) const
{
const real_t time = (*time_mem)(0);
const real_t period = (*time_mem)(1);
const real_t amplitude = (*time_mem)(2);
const auto detJ = mfem::future::det(J);
// time dependent force in x direction
const real_t force_amplitude = (time > 0.0) ? amplitude*sin(M_PI*time/period) : 0.0;
vecd_t force {0};//= vecd_t::Zero();
force(0) = force_amplitude;
return tuple{force * detJ * w};
}
};
struct DynamicVolForce
{
const real_t* time_mem;
DynamicVolForce(mfem::Vector& tm)
{
time_mem = tm.Read(); //get the device pointer
}
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const vec_t &x,
const matd_t &J,
const real_t &w
) const
{
const real_t time = *(time_mem+0);
const real_t period = *(time_mem+1);
const real_t amplitude = *(time_mem+2);
const real_t radius = *(time_mem+3);
const real_t L=*(time_mem+7);
const real_t t0=*(time_mem+8);
const real_t n=*(time_mem+9);
const real_t envelope_ampl= (time< L) ?pow(cos(M_PI*(time-t0)/L),n) : 0.0;
const real_t force_amplitude = (time > 0.0) ? amplitude*sin(2.0*M_PI*time/period) : 0.0;
vecd_t force {0};
// time dependent force in x direction
force(0) = force_amplitude*envelope_ampl;
//compute the distance from the center of the force application
real_t dist_sq = 0.0;
for (int i = 0; i < DI; i++)
{
const real_t diff = x(i) - *(time_mem+4+i);
dist_sq += diff * diff;
}
// apply the force only within the specified radius
if(dist_sq > radius*radius)
{
force(0) = 0.0;
}
const auto detJ = mfem::future::det(J);
return tuple{force * detJ * w};
}
struct Objective
{
/* data */
const real_t* obj_mem;
Objective(mfem::Vector& tm)
{
obj_mem = tm.Read(); //get the device pointer
}
// takes velocity and returns squared velocity
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const vec_t &x,
const mat_t &J,
const real_t &w
) const
{
const real_t time = *(obj_mem+0);
const real_t radius = *(obj_mem+1);
//compute the distance from the center of the objective circle/sphere
real_t dist_sq = 0.0;
scalar_t obj = 0.0;
for (int i = 0; i < DI; i++)
{
const real_t diff = x(i) - *(time_mem+2+i);
dist_sq += diff * diff;
obj += u(i) * u(i);
}
// apply the obj only within the specified radius
if(dist_sq > radius*radius)
{
obj = 0.0;
}
const auto detJ = mfem::future::det(J);
return tuple{obj * detJ * w};
}
};
struct ObjectiveGrad
{
/* data */
const real_t* obj_mem;
ObjectiveGrad(mfem::Vector& tm)
{
obj_mem = tm.Read(); //get the device pointer
}
// takes velocity and returns squared velocity
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const vecd_t &x,
const matd_t &J,
const real_t &w
) const
{
const real_t time = *(obj_mem+0);
const real_t radius = *(obj_mem+1);
//compute the distance from the center of the objective circle/sphere
scalar_t dist_sq = 0.0;
vecd_t obj_grad;
real_t objc = 1.0;
for (int i = 0; i < DI; i++)
{
const real_t diff = x(i) - *(time_mem+2+i);
dist_sq += diff * diff;
obj_grad(i) = 2.0 * u(i);
}
// apply the obj only within the specified radius
if(dist_sq > radius*radius)
{
objc = 0.0;
}
const auto detJ = mfem::future::det(J);
return tuple{objc* obj_grad * detJ * w};
}
};
};
};
class InterpolatedCoefficient : public mfem::Coefficient
{
public:
InterpolatedCoefficient(mfem::Coefficient &c1, mfem::Coefficient &c2, mfem::Coefficient &c3)
: coeff1(c1), coeff2(c2), coeff3(c3) {}
virtual double Eval(mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip) override
{
real_t c1=coeff1.Eval(T, ip);
real_t c2=coeff2.Eval(T, ip);
real_t dens=coeff3.Eval(T, ip);
return c2*dens + (1.0-dens)*c1;
}
private:
mfem::Coefficient &coeff1;
mfem::Coefficient &coeff2;
mfem::Coefficient &coeff3;
};
void LinearElasticityTimeDependentOperator::AssembleExplicit()
{
// define the mass differentiable operator
{
dfem_mass_op = std::make_unique<mfem::future::DifferentiableOperator>(
std::vector<mfem::future::FieldDescriptor>{ {FDispl, fespace.get()} },
std::vector<mfem::future::FieldDescriptor>{
{Dens1, ups.get()},
{Dens2, ups.get()},
{Density, ups.get()},
{Coords, mfes}
},
mesh);
dfem_mass_op->SetParameters({ dens1.get(), dens2.get(), density.get(), nodes });
const auto minputs =
mfem::future::tuple{
mfem::future::Value<FDispl>{},
mfem::future::Identity<Dens1>{},
mfem::future::Identity<Dens2>{},
mfem::future::Identity<Density>{},
mfem::future::Gradient<Coords>{},
mfem::future::Weight{}
};
const auto moutputs =
mfem::future::tuple{
mfem::future::Value<FDispl>{}
};
if (2 == space_dim)
{
typename QElasticityFunction<2>::Mass mass_func;
dfem_mass_op->AddDomainIntegrator(mass_func, minputs, moutputs, *ir, domain_attributes);
}
else if (3 == space_dim)
{
typename QElasticityFunction<3>::Mass mass_func;
dfem_mass_op->AddDomainIntegrator(mass_func, minputs, moutputs, *ir, domain_attributes);
}
}
// define the damp differentiable operator
{
dfem_damp_op = std::make_unique<mfem::future::DifferentiableOperator>(
std::vector<mfem::future::FieldDescriptor>{ {FVeloc, fespace.get()} },
std::vector<mfem::future::FieldDescriptor>{
{CMass1, ups.get()},
{CMass2, ups.get()},
{Density, ups.get()},
{Coords, mfes}
},
mesh);
dfem_damp_op->SetParameters({ cm1.get(), cm2.get(), density.get(), nodes });
const auto dinputs =
mfem::future::tuple{
mfem::future::Value<FVeloc>{},
mfem::future::Identity<CMass1>{},
mfem::future::Identity<CMass2>{},
mfem::future::Identity<Density>{},
mfem::future::Gradient<Coords>{},
mfem::future::Weight{}
};
const auto doutputs =
mfem::future::tuple{
mfem::future::Value<FVeloc>{}
};
if (2 == space_dim)
{
typename QElasticityFunction<2>::Mass damp_func;
dfem_damp_op->AddDomainIntegrator(damp_func, dinputs, doutputs, *ir, domain_attributes);
}
else if (3 == space_dim)
{
typename QElasticityFunction<3>::Mass damp_func;
dfem_damp_op->AddDomainIntegrator(damp_func, dinputs, doutputs, *ir, domain_attributes);
}
}
//define the volumetric force differentiable operator
{
dfem_vol_force_op = std::make_unique<mfem::future::DifferentiableOperator>(
std::vector<mfem::future::FieldDescriptor>{ {FDispl, fespace.get()} },
std::vector<mfem::future::FieldDescriptor>{
{Coords, mfes}
},
mesh);
dfem_vol_force_op->SetParameters({ nodes });
const auto finputs =
mfem::future::tuple{
mfem::future::Value<FDispl>{},
mfem::future::Value<Coords>{},
mfem::future::Gradient<Coords>{},
mfem::future::Weight{}
};
const auto foutputs =
mfem::future::tuple{
mfem::future::Value<FDispl>{}
};
if (2 == space_dim)
{
typename QElasticityFunction<2>::DynamicVolForce vol_force_func(vol_force_mem);
dfem_vol_force_op->AddDomainIntegrator(vol_force_func, finputs, foutputs, *ir, domain_attributes);
}
else if (3 == space_dim)
{
typename QElasticityFunction<3>::DynamicVolForce vol_force_func(vol_force_mem);
dfem_vol_force_op->AddDomainIntegrator(vol_force_func, finputs, foutputs, *ir, domain_attributes);
}
}
// define the linear elasticity differentiable operator
{
dfem_forward_op = std::make_unique<mfem::future::DifferentiableOperator>(
std::vector<mfem::future::FieldDescriptor>{ {FDispl, fespace.get()} },
std::vector<mfem::future::FieldDescriptor>{
{Lambda1, ups.get()},
{Mu1, ups.get()},
{Lambda2, ups.get()},
{Mu2, ups.get()},
{Density, ups.get()},
{Coords, mfes}
},
mesh);
dfem_forward_op->SetParameters({ l1.get(), m1.get(), l2.get(), m2.get(), density.get(), nodes });
const auto finputs =
mfem::future::tuple{
mfem::future::Gradient<FDispl>{},
mfem::future::Identity<Lambda1>{},
mfem::future::Identity<Mu1>{},
mfem::future::Identity<Lambda2>{},
mfem::future::Identity<Mu2>{},
mfem::future::Identity<Density>{},
mfem::future::Gradient<Coords>{},
mfem::future::Weight{}
};
const auto foutputs =
mfem::future::tuple{
mfem::future::Gradient<FDispl>{}
};
if (2 == space_dim)
{
typename QElasticityFunction<2>::Elasticity elasticity_func;
dfem_forward_op->AddDomainIntegrator(elasticity_func, finputs, foutputs, *ir, domain_attributes);
}
else if (3 == space_dim)
{
typename QElasticityFunction<3>::Elasticity elasticity_func;
dfem_forward_op->AddDomainIntegrator(elasticity_func, finputs, foutputs, *ir, domain_attributes);
}
}
//Spectral mass-matrix
{
InterpolatedCoefficient interp_dens1(*cdens1, *cdens2, *cdensity);
IntegrationRules gll_rules(0, Quadrature1D::GaussLobatto);
const IntegrationRule &ir_ni = gll_rules.Get(mesh.GetTypicalElementGeometry(),
2 * order - 1);
ParBilinearForm bf_lor(fespace.get());
auto *mv_blfi = new VectorMassIntegrator(interp_dens1);
mv_blfi->SetIntRule(&ir_ni);
//bf_lor.AddDomainIntegrator(new LumpedIntegrator(mv_blfi));
bf_lor.AddDomainIntegrator(mv_blfi);
bf_lor.Assemble();
bf_lor.Finalize();
M_lor.reset(bf_lor.ParallelAssemble());
}
// allocate the AMG preconditioner and CG solver
// for the mass matrix
{
amg = std::make_unique<HypreBoomerAMG>();
amg->SetPrintLevel(1);
amg->SetOperator(*M_lor);
cg = std::make_unique<CGSolver>(mesh.GetComm());
cg->SetRelTol(1e-10);
cg->SetAbsTol(1e-12);
cg->SetMaxIter(500);
cg->SetPrintLevel(0);
cg->SetOperator(*dfem_mass_op);
//cg->SetOperator(*M_lor);
cg->SetPreconditioner(*amg);
cg->iterative_mode=false;
}
//set the zero bdr conditions
{
Array<int> bdr_attr; bdr_attr.SetSize(mesh.bdr_attributes.Max());
bdr_attr=0;
for(const auto &it:zero_bdrs)
{
bdr_attr[it-1]=1.0;
}
fespace->GetEssentialTrueDofs(bdr_attr,ess_tdof_list);
}
}
void LinearElasticityTimeDependentOperator::Mult(const Vector &x,
Vector &y) const
{
real_t time = this->GetTime();
BlockVector bx(const_cast<Vector&>(x), block_true_offsets);
BlockVector by(y, block_true_offsets);
displ.GetTrueVector().Set(1.0,bx.GetBlock(0));
veloc.GetTrueVector().Set(1.0,bx.GetBlock(1));
//set zero BC
{
int N = ess_tdof_list.Size();
real_t *dp=displ.GetTrueVector().ReadWrite();
real_t *vp=veloc.GetTrueVector().ReadWrite();
const int *ep = ess_tdof_list.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE(int i) {
dp[ep[i]] = 0.0;
vp[ep[i]] = 0.0;
});
}
//displ.SetFromTrueVector();
//veloc.SetFromTrueVector();
by.GetBlock(0).Set(1.0, veloc.GetTrueVector()); // dx/dt = velocity
// compute the residual
// 1) add external volumetric forces
real_t* pvol_force_mem=vol_force_mem.HostReadWrite(); //get the host pointer
pvol_force_mem[0]=time; //set the current time to be pass to the integrator
vol_force_mem.Read(); //copy force_mem from host to device
// call the kernel computing f_ext
// dfem_vol_force_op->SetParameters({nodes}); // it is already set
dfem_vol_force_op->Mult(veloc.GetTrueVector(),res);
// 2) compute the mass proportional viscous damping term
// dfem_damp_op->SetParameters({ cm1.get(), cm2.get(), density.get(), nodes });
dfem_damp_op->Mult(veloc.GetTrueVector(), tmp.GetBlock(1));
res -= tmp.GetBlock(1);
// 3) add the stiffness proportional viscous damping term
// 4) add the elastic force term
dfem_forward_op->Mult(displ.GetTrueVector(),tmp.GetBlock(0));
res-= tmp.GetBlock(0);
//dfem_mass_op->SetParameters({dens1.get(), dens2.get(), density.get(), nodes});
cg->Mult(res, by.GetBlock(1)); // solve for acceleration
//by.GetBlock(1).Set(1.0,res);
//set zero grad BC
{
int N = ess_tdof_list.Size();
real_t *dp=by.GetBlock(0).ReadWrite();
real_t *vp=by.GetBlock(1).ReadWrite();
const int *ep = ess_tdof_list.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE(int i) {
dp[ep[i]] = 0.0;
vp[ep[i]] = 0.0;
});
}
}
/// y = (df/dx(x,t))^T * w
void LinearElasticityTimeDependentOperator::JacobianMultTranspose(const Vector &x,
const Vector &w,
Vector &y) const
{
//BlockVector bw(const_cast<Vector&>(w), block_true_offsets);
rhs.Set(1.0,w);
y=0.0;
BlockVector by(y, block_true_offsets);
Vector& lu=rhs.GetBlock(0);
Vector& lv=rhs.GetBlock(1);
//set zero grad BC
{
int N = ess_tdof_list.Size();
real_t *dp=lu.ReadWrite();
real_t *vp=lv.ReadWrite();
const int *ep = ess_tdof_list.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE(int i) {
dp[ep[i]] = 0.0;
vp[ep[i]] = 0.0;
});
}
res=0.0;
cg->Mult(lv,res); res*=(-1.0); //res=-M^{-1}*v
//res.Set(-1.0,lv);
//multiply by the elastic matrix
dfem_forward_op->Mult(res,by.GetBlock(0));
//multiply by the damping matrix
dfem_damp_op->Mult(res,by.GetBlock(1));
by.GetBlock(1).Add(1.0,lu); //add lu
//set zero BC
{
int N = ess_tdof_list.Size();
real_t *dp=by.GetBlock(0).ReadWrite();//displ adj
real_t *vp=by.GetBlock(1).ReadWrite();//veloc adj
const int *ep = ess_tdof_list.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE(int i) {
dp[ep[i]] = 0.0;
vp[ep[i]] = 0.0;
});
}
}
void LinearElasticityTimeDependentOperator::MultInvMass(const mfem::Vector &x, mfem::Vector &y) const
{
cg->Mult(x,y);
}
void LinearElasticityTimeDependentOperator::ImplicitSolve(
const real_t dt,
const Vector &x,
Vector &k)
{
}
template <int DI, typename scalar_t=real_t> struct QObjectiveFunction
{
using matd_t = tensor<scalar_t, DI, DI>;
using vecd_t = tensor<scalar_t, DI>;
using vec_t = tensor<real_t, DI>;
using mat_t = tensor<real_t, DI, DI>;
struct Objective1
{
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const real_t &co,
const matd_t &J,
const real_t &w) const
{
scalar_t rez;
rez=0.0;
for(int i=0;i<DI;i++)
{
rez=rez+u(i)*u(i);
}
rez=rez*co;
const auto detJ = mfem::future::det(J);
return tuple{rez * detJ * w};
}
};
struct Objective2
{
const real_t s1;
const real_t s2;
Objective2(real_t s1_ = real_t(1.0), real_t s2_ = real_t(1.0)):s1(s1_),s2(s2_)
{
}
MFEM_HOST_DEVICE inline auto operator()(const vecd_t &u,
const vecd_t &v,
const real_t &co,
const matd_t &J,
const real_t &w) const
{
scalar_t rez=0.0;
for(int i=0;i<DI;i++)
{
rez=rez+u(i)*u(i)*s1+v(i)*v(i)*s2;
}
const auto detJ = mfem::future::det(J);
return tuple{rez * detJ * w};
}
};
};
ExampleObjectiveIntegrand::ExampleObjectiveIntegrand(mfem::ParFiniteElementSpace* fes_,
std::shared_ptr<mfem::Coefficient> objc_):fes(fes_)
{
disp.SetSpace(fes); disp=0.0;
velo.SetSpace(fes); velo=0.0;
this->width=2*fes->GetTrueVSize(); //disp.Size() + veloc.Size()
this->height=1; //returns objective
grad=nullptr;
//set the block sizes for the solution, rhs and tmp vectors
block_true_offsets.SetSize(3);
block_true_offsets[0] = 0;
block_true_offsets[1] = fes->TrueVSize();
block_true_offsets[2] = fes->TrueVSize();
block_true_offsets.PartialSum();
SetCoefficients(objc_);
}
void ExampleObjectiveIntegrand::SetCoefficients( std::shared_ptr<mfem::Coefficient> objc)
{
co=objc;
mass.reset(new mfem::ParBilinearForm(fes));
mass->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
if(co.get()!=nullptr)
{
mfem::BilinearFormIntegrator* mi=new mfem::VectorMassIntegrator(*co);
mass->AddDomainIntegrator(mi);
}else{
mfem::BilinearFormIntegrator* mi=new mfem::VectorMassIntegrator();
mass->AddDomainIntegrator(mi);
}
mass->Assemble();
res.SetSize(fes->GetTrueVSize());
}
void ExampleObjectiveIntegrand::Mult(const Vector &x, Vector &y) const
{
res=0.0;
BlockVector bx(const_cast<Vector&>(x), block_true_offsets);
mass->TrueAddMult(bx.GetBlock(0),res);
//sum up the weighted values
real_t lp=mfem::InnerProduct(fes->GetComm(), res, bx.GetBlock(0));
res=0.0;
mass->TrueAddMult(bx.GetBlock(1),res);
real_t lv=mfem::InnerProduct(fes->GetComm(), res, bx.GetBlock(1));
y[0]=0.5*(lp+lv);
}
void ExampleObjectiveIntegrand::EvalGradient(const Vector &x, Vector &grad) const
{
BlockVector bx(const_cast<Vector&>(x), block_true_offsets);
BlockVector by(grad, block_true_offsets); by=0.0;
mass->TrueAddMult(bx.GetBlock(0),by.GetBlock(0));
mass->TrueAddMult(bx.GetBlock(1),by.GetBlock(1));
}
@@ -0,0 +1,289 @@
#ifndef LINEAR_ELASTICITY_HPP
#define LINEAR_ELASTICITY_HPP
#include "mfem.hpp"
class LinearElasticityTimeDependentOperator : public mfem::TimeDependentOperator
{
public:
LinearElasticityTimeDependentOperator(mfem::ParMesh &mesh_, int vorder =1);
virtual ~LinearElasticityTimeDependentOperator() override
{
}
void MultInvMass(const mfem::Vector &x, mfem::Vector &y) const;
virtual void Mult(const mfem::Vector &x, mfem::Vector &y) const override;
void JacobianMultTranspose(const mfem::Vector &x,
const mfem::Vector &w,
mfem::Vector &y) const override;
virtual void ImplicitSolve(const mfem::real_t dt,
const mfem::Vector &x,
mfem::Vector &k) override;
// Assemble the explicit operators
// must be called after setting all material coefficients
// and before time stepping
void AssembleExplicit();
void SetDensity(mfem::Coefficient &rho)
{
density = std::make_shared<mfem::CoefficientVector>(*qs, mfem::CoefficientStorage::FULL);
cdensity = &rho;
density->Project(rho);
}
// l1, m1 are the Lame parameters for material 1
// l2, m2 are the Lame parameters for material 2
void SetElasticityCoefficients(mfem::Coefficient& l1_,
mfem::Coefficient& m1_,
mfem::Coefficient& l2_,
mfem::Coefficient& m2_)
{
l1.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
l2.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
m1.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
m2.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
l1->Project(l1_);
m1->Project(m1_);
l2->Project(l2_);
m2->Project(m2_);
}
// rho1 and rho2 are the density coefficients for material 1 and 2
void SetDensityMaterialCoefficients(mfem::Coefficient& rho1_,
mfem::Coefficient& rho2_)
{
dens1.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
dens2.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
cdens1 = &rho1_;
cdens2 = &rho2_;
dens1->Project(rho1_);
dens2->Project(rho2_);
}
// c1 and c2 are velocity proportional damping coefficients for material 1 and 2
// both of them are considered to be time dependent
void SetDampingMaterialCoefficients(mfem::Coefficient& c1_,
mfem::Coefficient& c2_)
{
cm1.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
cm2.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
cm1->Project(c1_);
cm2->Project(c2_);
}
// dl1, dm1 are the strain velocity proportional damping coefficients
// for material 1 and dl2, dm2 for material 2
// all of them are considered to be time dependent
void SetDampingMaterialCoefficients(mfem::Coefficient& dl1_,
mfem::Coefficient& dm1_,
mfem::Coefficient& dl2_,
mfem::Coefficient& dm2_)
{
dl1.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
dl2.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
dm1.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
dm2.reset(new mfem::CoefficientVector(*qs, mfem::CoefficientStorage::FULL));
dl1->Project(dl1_);
dm1->Project(dm1_);
dl2->Project(dl2_);
dm2->Project(dm2_);
}
mfem::ParGridFunction& GetDisplacement() { return displ; }
mfem::ParGridFunction& GetVelocity() { return veloc; }
mfem::Array<int>& GetTrueBlockOffsets(){ return block_true_offsets;}
mfem::Vector& GetState(){return sol;}
mfem::ParFiniteElementSpace* GetFESpace(){ return fespace.get();}
void SetZeroBdr(int bdr_attr)
{
zero_bdrs.insert(bdr_attr);
}
void SetBdrLoad(int attr)
{
bdr_loads_markers.insert(attr);
}
void SetVolForce(mfem::real_t period, mfem::real_t amplitude, mfem::real_t rad,
mfem::real_t xc=0.0, mfem::real_t yc=0.0, mfem::real_t zc=0.0,
mfem::real_t L=5.0, mfem::real_t t0=0.0, mfem::real_t n=2.0)
{
// copy data to the host
mfem::real_t* pvol_force_mem = vol_force_mem.HostReadWrite();
pvol_force_mem[1] = period;
pvol_force_mem[2] = amplitude;
pvol_force_mem[3] = rad;
pvol_force_mem[4] = xc;
pvol_force_mem[5] = yc;
pvol_force_mem[6] = zc;
pvol_force_mem[7] = L;
pvol_force_mem[8] = t0;
pvol_force_mem[9] = n;
// copy data to the device
vol_force_mem.Read();
}
private:
mfem::ParMesh &mesh;
int order;
std::unique_ptr<mfem::FiniteElementCollection> fec;
std::unique_ptr<mfem::ParFiniteElementSpace> fespace;
int dim;
int space_dim;
int myrank;
mfem::ParGridFunction *nodes;
mfem::ParFiniteElementSpace *mfes;
mfem::Array<int> domain_attributes;
const mfem::IntegrationRule *ir;
mutable mfem::ParGridFunction displ;
mutable mfem::ParGridFunction veloc;
mutable mfem::ParGridFunction accel;
mutable mfem::BlockVector sol;
mutable mfem::BlockVector rhs;
mutable mfem::BlockVector tmp;
mutable mfem::Vector res;
mfem::Array<int> block_true_offsets;
std::unique_ptr<mfem::future::UniformParameterSpace> ups;
std::unique_ptr<mfem::QuadratureSpace> qs;
std::unique_ptr<mfem::FaceQuadratureSpace> fqs;
// linear elasticty coefficients in dFEM form
// l1, m1 - material 1
// l2, m2 - material 2
std::unique_ptr<mfem::CoefficientVector> l1, l2;
std::unique_ptr<mfem::CoefficientVector> m1, m2;
// linear elasticity damping coefficients in dFEM form
// strain proportional damping for material 1 and 2
std::unique_ptr<mfem::CoefficientVector> dl1, dl2;
std::unique_ptr<mfem::CoefficientVector> dm1, dm2;
// damping mass coefficients in dFEM form
// (velocity proportional damping) for material 1 and 2
std::unique_ptr<mfem::CoefficientVector> cm1, cm2;
// density coefficients in dFEM form for material 1 and 2
std::unique_ptr<mfem::CoefficientVector> dens1, dens2;
mfem::Coefficient *cdens1, *cdens2;
// density coefficient for topology optimization
std::shared_ptr<mfem::CoefficientVector> density;
mfem::Coefficient *cdensity;
static constexpr int FDispl = 0; //grid function displacement
static constexpr int FVeloc = 1; //grid function velocity
// elasticity Coefficient Vectors
static constexpr int Lambda1 = 2, Lambda2 = 3, Mu1 = 4, Mu2 = 5;
// damping Coefficient Vectors
static constexpr int DLambda1 = 6, DMu1 = 7, DLambda2 = 8, DMu2 = 9;
static constexpr int CMass1 = 10, CMass2 = 11; // damping mass coeff vectors
static constexpr int Dens1 = 12, Dens2 = 13; // density Coefficient Vectors
// density for topology optimization
static constexpr int Density = 14; // coefficient vector
static constexpr int Coords = 15; // coordinates grid function
// DFEM forward related definitions
std::unique_ptr<mfem::future::DifferentiableOperator> dfem_forward_op;
std::unique_ptr<mfem::future::DifferentiableOperator> dfem_mass_op;
std::unique_ptr<mfem::future::DifferentiableOperator> dfem_damp_op;
std::unique_ptr<mfem::future::DifferentiableOperator> dfem_vol_force_op;
std::unique_ptr<mfem::HypreParMatrix> M_lor;
std::unique_ptr<mfem::CGSolver> cg;
std::unique_ptr<mfem::HypreBoomerAMG> amg;
// zero bdrs
std::set<int> zero_bdrs;
// time dependent memory vector for dynamic force
// the force is applied on all boundary attributes in bdr_loads_markers
std::set<int> bdr_loads_markers;
mutable mfem::Vector bdr_force_mem; // [0] - time, [1] - period, [2] - amplitude
// volumetric force parameters
// A*sin(2*pi*t/T)*cos^n (pi (t-t_0)/L)
// [0] - time, [1] - period, [2] - amplitude, [3] - radius
// [4],[5],[6] - point coordinates of the center of the force application
// [7] - L total train length - could be proportional to the period [1]
// [8] - t_0 center of the train
// [9] - n the envelope power
mutable mfem::Vector vol_force_mem;
// zero bdr dofs - constructed during the corrsponding Assemble calls
mfem::Array<int> ess_tdof_list;
};
class ExampleObjectiveIntegrand: public mfem::Operator
{
public:
ExampleObjectiveIntegrand(mfem::ParFiniteElementSpace* fes_,
std::shared_ptr<mfem::Coefficient> objc={});
void SetCoefficients( std::shared_ptr<mfem::Coefficient> objc);
//evaluates the QoIs y[1] for a given state x[2 x fes_->GetTrueVSize()]
virtual void Mult(const mfem::Vector &x, mfem::Vector &y) const override;
mfem::real_t EvalScalar(const mfem::Vector &x) const
{
mfem::Vector y(1);
Mult(x, y);
return y[0];
}
void EvalGradient(const mfem::Vector &x, mfem::Vector &grad) const;
private:
std::shared_ptr<mfem::Coefficient> co;
mfem::ParFiniteElementSpace* fes;
mutable mfem::ParGridFunction disp;
mutable mfem::ParGridFunction velo;
mfem::Operator* grad;
mfem::Array<int> block_true_offsets;
mutable mfem::Vector res;
std::unique_ptr<mfem::ParBilinearForm> mass;
};
#endif // LINEAR_ELASTICITY_HPP
File diff suppressed because it is too large Load Diff
+545
View File
@@ -0,0 +1,545 @@
#include "linear_elasticity.hpp"
#include "../chpt/dynamic_checkpointing.hpp"
#include "../chpt/fixed_slot_checkpoint_storage.hpp"
#include <cmath>
#include <iomanip>
#include <cstring>
using namespace mfem;
using namespace std;
constexpr auto MESH_TRI = MFEM_SOURCE_DIR "/miniapps/mtop/examples/dyn_hex2d_tri.msh";
constexpr auto MESH_QUAD = MFEM_SOURCE_DIR "/miniapps/mtop/examples/dyn_hex2d_quad.msh";
struct State
{
mfem::real_t time = 0.0; //time of the state
mfem::real_t dt=0.0;
mfem::real_t obj = 0.0; //accumulated objective
mfem::BlockVector v; //state of the system
};
// Snapshot = *view* (non-owning) used only during Store() packing and Read() callback
struct StateSnapshotView
{
mfem::real_t time = 0.0;
mfem::real_t dt=0.0;
mfem::real_t obj = 0.0;
// Points to n*sizeof(real_t) bytes:
// - during Store(): points to current State::v data (host)
// - during Read(): points into the storage slot bytes (valid only during callback)
const unsigned char *v_bytes = nullptr;
};
class StateSnapshotViewPacker
{
public:
explicit StateSnapshotViewPacker(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "StateSnapshotViewPacker: n must be > 0.");
}
std::size_t SlotBytes() const
{
return (std::size_t)(3 + n_) * sizeof(mfem::real_t);
}
void Pack(const StateSnapshotView &s, void *dst) const
{
MFEM_VERIFY(dst != nullptr, "Pack: dst is null.");
MFEM_VERIFY(s.v_bytes != nullptr, "Pack: snapshot v_bytes is null.");
unsigned char *b = static_cast<unsigned char*>(dst);
std::memcpy(b + 0*sizeof(mfem::real_t), &s.time, sizeof(mfem::real_t));
std::memcpy(b + 1*sizeof(mfem::real_t), &s.dt, sizeof(mfem::real_t));
std::memcpy(b + 2*sizeof(mfem::real_t), &s.obj, sizeof(mfem::real_t));
std::memcpy(b + 3*sizeof(mfem::real_t),
s.v_bytes,
(std::size_t)n_ * sizeof(mfem::real_t));
}
// Important: Unpack returns a *view* pointing into src bytes (no allocation).
void Unpack(const void *src, StateSnapshotView &out) const
{
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
const unsigned char *b = static_cast<const unsigned char*>(src);
std::memcpy(&out.time, b + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(&out.dt, b + 1*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(&out.obj, b + 2*sizeof(mfem::real_t), sizeof(mfem::real_t));
out.v_bytes = b + 3*sizeof(mfem::real_t);
}
int VectorSize() const { return n_; }
private:
int n_ = 0;
};
struct AdjState
{
mfem::real_t time;
mfem::real_t obj;
mfem::BlockVector adj;
};
///////////////////////////////////////////////////////////////////////////////
/// \brief The IsoElasticyLambdaCoeff class converts E modulus of elasticity
/// and Poisson's ratio to Lame's lambda coefficient
class IsoElasticyLambdaCoeff : public mfem::Coefficient
{
mfem::Coefficient *E, *nu;
public:
/// Constructor - takes as inputs E modulus and Poisson's ratio
IsoElasticyLambdaCoeff(mfem::Coefficient *E,
mfem::Coefficient *nu):
E(E), nu(nu) { }
/// Evaluates the Lame's lambda coefficient
real_t Eval(mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip) override
{
const real_t EE = E->Eval(T, ip);
const real_t nn = nu->Eval(T, ip);
constexpr auto Lambda = [](const real_t E, const real_t nu)
{
return E * nu / (1.0 + nu) / (1.0 - 2.0 * nu);
};
return Lambda(EE, nn);
}
};
///////////////////////////////////////////////////////////////////////////////
/// \brief The IsoElasticySchearCoeff class converts E modulus of elasticity
/// and Poisson's ratio to Shear coefficient
///
class IsoElasticySchearCoeff : public mfem::Coefficient
{
mfem::Coefficient *E, *nu;
public:
/// Constructor - takes as inputs E modulus and Poisson's ratio
IsoElasticySchearCoeff(mfem::Coefficient *E_, mfem::Coefficient *nu_):
E(E_), nu(nu_) { }
/// Evaluates the shear coefficient coefficient
real_t Eval(mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip) override
{
const real_t EE = E->Eval(T, ip);
const real_t nn = nu->Eval(T, ip);
constexpr auto Schear = [](const real_t E, const real_t nu)
{
return E / (2.0 * (1.0 + nu));
};
return Schear(EE, nn);
}
};
int main(int argc, char *argv[])
{
// Initialize MPI and HYPRE.
Mpi::Init();
Hypre::Init();
// Parse command-line options.
const char *mesh_file = MESH_QUAD;
const char *device_config = "cpu";
int order = 3;
bool mesh_tri = false;
bool mesh_quad = false;
int par_ref_levels = 1;
bool paraview = true;
bool visualization = true;
int ode_solver_type = 4;
real_t Tfinal = 0.07;
real_t dt = 0.005;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree) or -1 for"
" isoparametric space.");
args.AddOption(&mesh_tri, "-tri", "--triangular", "-no-tri",
"--no-triangular", "Enable or not triangular mesh.");
args.AddOption(&mesh_quad, "-quad", "--quadrilateral", "-no-quad",
"--no-quadrilateral", "Enable or not quadrilateral mesh.");
args.AddOption(&par_ref_levels, "-prl", "--par-ref-levels",
"Number of parallel mesh refinement levels.");
args.AddOption(&paraview, "-pa", "--paraview", "-no-pa",
"--no-paraview", "Enable or not Paraview output.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization", "Enable or not visualization.");
args.AddOption(&ode_solver_type, "-s", "--ode-solver",
ODESolver::Types.c_str());
args.AddOption(&Tfinal, "-T",
"--tfinal", "Terminate when accumulated time reaches Tfinal.");
args.AddOption(&dt, "-dt", "--dt", "Time step.");
args.ParseCheck();
// Enable hardware devices such as GPUs, and programming models such as
// CUDA, OCCA, RAJA and OpenMP based on command line options.
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// Read the (serial) mesh from the given mesh file on all processors. We
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
// and volume meshes with the same code.
Mesh mesh(mesh_tri ? MESH_TRI : mesh_quad ? MESH_QUAD : mesh_file, 1, 1);
const int dim = mesh.Dimension();
// Refine the serial mesh on all processors to increase the resolution. In
// this example we do 'ref_levels' of uniform refinement. We choose
// 'ref_levels' to be the largest number that gives a final mesh with no
// more than 1000 elements.
{
const int ref_levels =
(int)floor(log(1000. / mesh.GetNE()) / log(2.) / dim);
for (int l = 0; l < ref_levels; l++) { mesh.UniformRefinement(); }
}
if (Mpi::Root())
{
std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
}
// Define a parallel mesh by a partitioning of the serial mesh. Refine
// this mesh further in parallel to increase the resolution. Once the
// parallel mesh is defined, the serial mesh can be deleted.
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
for (int l = 0; l < par_ref_levels; l++) { pmesh.UniformRefinement(); }
// Allocate the time dependent linear elasticity operator
LinearElasticityTimeDependentOperator lin_elasticity_op(pmesh, order);
// Set the material coefficients
ConstantCoefficient rho_coef(0.5); // density coefficient for topology optimization
// Set elasticity coefficients for material 1 and 2
ConstantCoefficient E1(0.1);
ConstantCoefficient E2(1.0);
ConstantCoefficient nu1(0.3);
ConstantCoefficient nu2(0.3);
//Lame coefficients
IsoElasticyLambdaCoeff lambda1(&E1, &nu1);
IsoElasticySchearCoeff mu1(&E1, &nu1);
IsoElasticyLambdaCoeff lambda2(&E2, &nu2);
IsoElasticySchearCoeff mu2(&E2, &nu2);
// Set density coefficients for material 1 and 2
ConstantCoefficient dens1_coef(0.5);
ConstantCoefficient dens2_coef(1.0);
// Set damping coefficients
ProductCoefficient cm1_coef(0.02, dens1_coef);
ProductCoefficient cm2_coef(0.02, dens2_coef);
ProductCoefficient cl1_coef(0.01, lambda1);
ProductCoefficient cmu1_coef(0.01, mu1);
ProductCoefficient cl2_coef(0.01, lambda2);
ProductCoefficient cmu2_coef(0.01, mu2);
lin_elasticity_op.SetElasticityCoefficients(lambda1, mu1, lambda2, mu2);
lin_elasticity_op.SetDensityMaterialCoefficients(dens1_coef, dens2_coef);
lin_elasticity_op.SetDampingMaterialCoefficients(cm1_coef, cm2_coef);
lin_elasticity_op.SetDampingMaterialCoefficients(cl1_coef, cmu1_coef,
cl2_coef, cmu2_coef);
lin_elasticity_op.SetDensity(rho_coef);
//set bottom bdr to zero (both the velocities and the displacements)
lin_elasticity_op.SetZeroBdr(1);
lin_elasticity_op.SetVolForce(1.0 /*period*/, 1.0 /*amplitude*/, 0.2 /*radius*/,
0.0 /*x center*/ , 0.0 /*y center*/, 0.0 /*z center*/,
5.0 /* train length*/, 2.5 /*center of the train*/, 2.0 /*power*/);
lin_elasticity_op.AssembleExplicit();
// test mult explicit
{
BlockVector tst; tst.Update(lin_elasticity_op.GetTrueBlockOffsets());
tst=0.0; //tst.Randomize();
tst.UseDevice(true); tst.Read();
BlockVector grd; grd.Update(lin_elasticity_op.GetTrueBlockOffsets());
grd=0.0;
lin_elasticity_op.Mult(tst,grd);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(grd.GetBlock(1));
}
std::shared_ptr<ExampleObjectiveIntegrand> eobj=
std::make_shared<ExampleObjectiveIntegrand>(lin_elasticity_op.GetFESpace());
//set the objective for the integration process
ParaViewDataCollection paraview_dc("isoel", &pmesh);
paraview_dc.SetPrefixPath("ParaView");
paraview_dc.SetLevelsOfDetail(order);
paraview_dc.SetDataFormat(VTKFormat::BINARY);
paraview_dc.SetHighOrderOutput(true);
paraview_dc.RegisterField("disp", &(lin_elasticity_op.GetDisplacement()));
paraview_dc.RegisterField("velo", &(lin_elasticity_op.GetVelocity()));
// 4. Define the ODE solver used for time integration. Several explicit
// Runge-Kutta methods are available.
unique_ptr<ODESolver> ode_solver = ODESolver::Select(ode_solver_type);
lin_elasticity_op.SetTime(0.0);
ode_solver->Init(lin_elasticity_op);
//random perturbation of the initial conditions
BlockVector p; p.Update(lin_elasticity_op.GetTrueBlockOffsets()); p=0.0;
Vector tmpv(p.GetBlock(0)); tmpv=0.0;
//Forward computations
{
int s=10; //number of snapshots to be stored by the checkpointing process
//define the packer object
int n=lin_elasticity_op.GetState().Size();
StateSnapshotViewPacker packer(n);
// storage stores StateSnapshotView snapshots using fixed-size slots
using Storage=mfem::FixedSlotMemoryCheckpointStorage<StateSnapshotView, StateSnapshotViewPacker>;
Storage storage(s, packer);
// Snapshot type is StateSnapshotView
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshotView, Storage>;
Checkpointing ckpt(s, storage);
// Returns view of the State and avoids data transfer
auto make_snapshot = [&](const State &u) -> StateSnapshotView
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
// Ensure host access if MFEM device is in use:
const mfem::real_t *vh = u.v.HostRead();
StateSnapshotView snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v_bytes = reinterpret_cast<const unsigned char*>(vh);
return snap;
};
//Transfers data from the snaphot view to the State u_out.
auto restore_snapshot = [&](const StateSnapshotView &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
mfem::real_t *vh = u_out.v.HostWrite();
std::memcpy(vh,
snap.v_bytes,
(std::size_t)n * sizeof(mfem::real_t));
//make sure that the date is on the device
u_out.v.Read(true);
};
using Step = mfem::DynamicCheckpointing<StateSnapshotView, Storage>::Step;
//execute one integration step
auto primal_step = [&](State &u_st, Step i)
{
//begin with curent state u_st
real_t t=u_st.time;
real_t ldt=dt;
real_t obj=u_st.obj;
//make sure the integration does not overjump Tfinal
if((t+ldt)>Tfinal){
ldt=Tfinal-t;
}
//advance u_st
ode_solver->Step(u_st.v,t,ldt);
//update objective
obj=eobj->EvalScalar(u_st.v);
//return updated u_st
u_st.dt=t-u_st.time;
u_st.time=t;
u_st.obj=obj;
};
auto adjoint_step = [&](AdjState &adj_st, const State &u_st, Step i)
{
real_t t=u_st.time;
real_t ldt=u_st.dt;
real_t obj=u_st.obj;
ode_solver->EnableAdjoint(mfem::ODESolver::AdjointMode::Discrete);
ode_solver->SetSolution(u_st.v,t);
ode_solver->AdjointStep(adj_st.adj,t,ldt);
ode_solver->EnableAdjoint(mfem::ODESolver::AdjointMode::None);
adj_st.time=t;
adj_st.obj=obj;
};
BlockVector x0; x0.Update(lin_elasticity_op.GetTrueBlockOffsets());
x0=0.0;
//optimization loop
for(int ii=0;ii<1;ii++){
ParaViewDataCollection paraview_dc("frw", &pmesh);
paraview_dc.SetPrefixPath("ParaView");
paraview_dc.SetLevelsOfDetail(order);
paraview_dc.SetDataFormat(VTKFormat::BINARY);
paraview_dc.SetHighOrderOutput(true);
paraview_dc.RegisterField("disp", &(lin_elasticity_op.GetDisplacement()));
paraview_dc.RegisterField("velo", &(lin_elasticity_op.GetVelocity()));
State u;
//u.v.SetSize(lin_elasticity_op.GetState().Size());
u.v.Update(lin_elasticity_op.GetTrueBlockOffsets());
u.obj=0.0;
u.time=0.0;
u.dt=0.0;
//set initial state to 0
u.v=0.0;
u.v.Set(1.0,x0);
// Forward sweep (unknown number of steps)
real_t t = 0.0;
Step i = 0;
paraview_dc.SetCycle(0);
paraview_dc.SetTime(t);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(u.v.GetBlock(1));
lin_elasticity_op.GetDisplacement().SetFromTrueDofs(u.v.GetBlock(0));
paraview_dc.Save();
while(t<Tfinal)
{
ckpt.ForwardStep(i,u, primal_step, make_snapshot);
t=u.time;
++i;
if((i%5)==0){
paraview_dc.SetCycle(i+1);
paraview_dc.SetTime(t);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(u.v.GetBlock(1));
lin_elasticity_op.GetDisplacement().SetFromTrueDofs(u.v.GetBlock(0));
paraview_dc.Save();
}
}
//Do one more time step without checkpointing
//advance u_st
ode_solver->Step(u.v,t,dt);
//update objective
real_t obj=eobj->EvalScalar(u.v);
{
paraview_dc.SetCycle(i+1);
paraview_dc.SetTime(t);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(u.v.GetBlock(1));
lin_elasticity_op.GetDisplacement().SetFromTrueDofs(u.v.GetBlock(0));
paraview_dc.Save();
}
if(Mpi::Root())
{
std::cout<<"Total number of steps i="<<i<<" t="<<t<<" obj="<<obj<<std::endl;
std::cout<<"Start adjoint steps!"<<std::endl;
}
ParGridFunction adisp(lin_elasticity_op.GetDisplacement());
ParGridFunction avelo(lin_elasticity_op.GetVelocity());
ParaViewDataCollection paraview_ac("adj", &pmesh);
paraview_ac.SetPrefixPath("ParaView");
paraview_ac.SetLevelsOfDetail(order);
paraview_ac.SetDataFormat(VTKFormat::BINARY);
paraview_ac.SetHighOrderOutput(true);
paraview_ac.RegisterField("adisp", &(adisp));
paraview_ac.RegisterField("avelo", &(avelo));
// define the adjoint state
AdjState adj_st;
//adj_st.adj.SetSize(lin_elasticity_op.GetState().Size());
adj_st.adj.Update(lin_elasticity_op.GetTrueBlockOffsets());
adj_st.adj=0.0;
eobj->EvalGradient(u.v,adj_st.adj);
adj_st.obj=obj;
adj_st.time=t;
paraview_ac.SetCycle(i);
paraview_ac.SetTime(t);
lin_elasticity_op.MultInvMass(adj_st.adj.GetBlock(0),tmpv);
adisp.SetFromTrueDofs(tmpv);
lin_elasticity_op.MultInvMass(adj_st.adj.GetBlock(1),tmpv);
avelo.SetFromTrueDofs(tmpv);
paraview_ac.Save();
//step backward
const Step m=i;
//tmp state for stepping backward
State u_wrk;
//u_wrk.v.SetSize(lin_elasticity_op.GetState().Size());
u_wrk.v.Update(lin_elasticity_op.GetTrueBlockOffsets());
u_wrk.obj=0.0;
u_wrk.time=0.0;
u_wrk.dt=0.0;
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i,adj_st, u_wrk,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if((i%5)==0){
paraview_ac.SetCycle(i);
paraview_ac.SetTime(adj_st.time);
//adisp.SetFromTrueDofs(adj_st.adj.GetBlock(0));
//avelo.SetFromTrueDofs(adj_st.adj.GetBlock(1));
lin_elasticity_op.MultInvMass(adj_st.adj.GetBlock(0),tmpv);
adisp.SetFromTrueDofs(tmpv);
lin_elasticity_op.MultInvMass(adj_st.adj.GetBlock(1),tmpv);
avelo.SetFromTrueDofs(tmpv);
paraview_ac.Save();
}
if (i == 0) { break; }
}
lin_elasticity_op.MultInvMass(adj_st.adj.GetBlock(0),tmpv);
x0.GetBlock(0).Add(-0.1,tmpv);
lin_elasticity_op.MultInvMass(adj_st.adj.GetBlock(1),tmpv);
x0.GetBlock(1).Add(-0.1,tmpv);
}
}
return EXIT_SUCCESS;
}
+546
View File
@@ -0,0 +1,546 @@
#include "linear_elasticity.hpp"
#include "../chpt/dynamic_checkpointing.hpp"
#include "../chpt/fixed_slot_checkpoint_storage.hpp"
#include <cmath>
#include <iomanip>
#include <cstring>
using namespace mfem;
using namespace std;
constexpr auto MESH_TRI = MFEM_SOURCE_DIR "/miniapps/mtop/examples/dyn_hex2d_tri.msh";
constexpr auto MESH_QUAD = MFEM_SOURCE_DIR "/miniapps/mtop/examples/dyn_hex2d_quad.msh";
struct State
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
mfem::Vector v;
};
// Snapshot = *view* (non-owning) used only during Store() packing and Read() callback
struct StateSnapshotView
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
// Points to n*sizeof(real_t) bytes:
// - during Store(): points to current State::v data (host)
// - during Read(): points into the storage slot bytes (valid only during callback)
const unsigned char *v_bytes = nullptr;
};
class StateSnapshotViewPacker
{
public:
explicit StateSnapshotViewPacker(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "StateSnapshotViewPacker: n must be > 0.");
}
std::size_t SlotBytes() const
{
return (std::size_t)(2 + n_) * sizeof(mfem::real_t);
}
void Pack(const StateSnapshotView &s, void *dst) const
{
MFEM_VERIFY(dst != nullptr, "Pack: dst is null.");
MFEM_VERIFY(s.v_bytes != nullptr, "Pack: snapshot v_bytes is null.");
unsigned char *b = static_cast<unsigned char*>(dst);
std::memcpy(b, &s.time, sizeof(mfem::real_t));
std::memcpy(b + sizeof(mfem::real_t), &s.obj, sizeof(mfem::real_t));
std::memcpy(b + 2*sizeof(mfem::real_t),
s.v_bytes,
(std::size_t)n_ * sizeof(mfem::real_t));
}
// Important: Unpack returns a *view* pointing into src bytes (no allocation).
void Unpack(const void *src, StateSnapshotView &out) const
{
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
const unsigned char *b = static_cast<const unsigned char*>(src);
std::memcpy(&out.time, b, sizeof(mfem::real_t));
std::memcpy(&out.obj, b + sizeof(mfem::real_t), sizeof(mfem::real_t));
out.v_bytes = b + 2*sizeof(mfem::real_t);
}
int Size() const { return n_; }
private:
int n_ = 0;
};
struct AdjState
{
mfem::real_t time;
mfem::real_t obj;
mfem::Vector adj;
mfem::Vector grd;
};
///////////////////////////////////////////////////////////////////////////////
/// \brief The IsoElasticyLambdaCoeff class converts E modulus of elasticity
/// and Poisson's ratio to Lame's lambda coefficient
class IsoElasticyLambdaCoeff : public mfem::Coefficient
{
mfem::Coefficient *E, *nu;
public:
/// Constructor - takes as inputs E modulus and Poisson's ratio
IsoElasticyLambdaCoeff(mfem::Coefficient *E,
mfem::Coefficient *nu):
E(E), nu(nu) { }
/// Evaluates the Lame's lambda coefficient
real_t Eval(mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip) override
{
const real_t EE = E->Eval(T, ip);
const real_t nn = nu->Eval(T, ip);
constexpr auto Lambda = [](const real_t E, const real_t nu)
{
return E * nu / (1.0 + nu) / (1.0 - 2.0 * nu);
};
return Lambda(EE, nn);
}
};
///////////////////////////////////////////////////////////////////////////////
/// \brief The IsoElasticySchearCoeff class converts E modulus of elasticity
/// and Poisson's ratio to Shear coefficient
///
class IsoElasticySchearCoeff : public mfem::Coefficient
{
mfem::Coefficient *E, *nu;
public:
/// Constructor - takes as inputs E modulus and Poisson's ratio
IsoElasticySchearCoeff(mfem::Coefficient *E_, mfem::Coefficient *nu_):
E(E_), nu(nu_) { }
/// Evaluates the shear coefficient coefficient
real_t Eval(mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip) override
{
const real_t EE = E->Eval(T, ip);
const real_t nn = nu->Eval(T, ip);
constexpr auto Schear = [](const real_t E, const real_t nu)
{
return E / (2.0 * (1.0 + nu));
};
return Schear(EE, nn);
}
};
int main(int argc, char *argv[])
{
// Initialize MPI and HYPRE.
Mpi::Init();
Hypre::Init();
// Parse command-line options.
const char *mesh_file = MESH_QUAD;
const char *device_config = "cpu";
int order = 3;
bool mesh_tri = false;
bool mesh_quad = false;
int par_ref_levels = 1;
bool paraview = true;
bool visualization = true;
int ode_solver_type = 4;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree) or -1 for"
" isoparametric space.");
args.AddOption(&mesh_tri, "-tri", "--triangular", "-no-tri",
"--no-triangular", "Enable or not triangular mesh.");
args.AddOption(&mesh_quad, "-quad", "--quadrilateral", "-no-quad",
"--no-quadrilateral", "Enable or not quadrilateral mesh.");
args.AddOption(&par_ref_levels, "-prl", "--par-ref-levels",
"Number of parallel mesh refinement levels.");
args.AddOption(&paraview, "-pa", "--paraview", "-no-pa",
"--no-paraview", "Enable or not Paraview output.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization", "Enable or not visualization.");
args.AddOption(&ode_solver_type, "-s", "--ode-solver",
ODESolver::Types.c_str());
args.ParseCheck();
// Enable hardware devices such as GPUs, and programming models such as
// CUDA, OCCA, RAJA and OpenMP based on command line options.
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// Read the (serial) mesh from the given mesh file on all processors. We
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
// and volume meshes with the same code.
Mesh mesh(mesh_tri ? MESH_TRI : mesh_quad ? MESH_QUAD : mesh_file, 1, 1);
const int dim = mesh.Dimension();
// Refine the serial mesh on all processors to increase the resolution. In
// this example we do 'ref_levels' of uniform refinement. We choose
// 'ref_levels' to be the largest number that gives a final mesh with no
// more than 1000 elements.
{
const int ref_levels =
(int)floor(log(1000. / mesh.GetNE()) / log(2.) / dim);
for (int l = 0; l < ref_levels; l++) { mesh.UniformRefinement(); }
}
if (Mpi::Root())
{
std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
}
// Define a parallel mesh by a partitioning of the serial mesh. Refine
// this mesh further in parallel to increase the resolution. Once the
// parallel mesh is defined, the serial mesh can be deleted.
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
for (int l = 0; l < par_ref_levels; l++) { pmesh.UniformRefinement(); }
// Allocate the time dependent linear elasticity operator
LinearElasticityTimeDependentOperator lin_elasticity_op(pmesh, order);
// Set the material coefficients
ConstantCoefficient rho_coef(0.5); // density coefficient for topology optimization
// Set elasticity coefficients for material 1 and 2
ConstantCoefficient E1(0.1);
ConstantCoefficient E2(1.0);
ConstantCoefficient nu1(0.3);
ConstantCoefficient nu2(0.3);
//Lame coefficients
IsoElasticyLambdaCoeff lambda1(&E1, &nu1);
IsoElasticySchearCoeff mu1(&E1, &nu1);
IsoElasticyLambdaCoeff lambda2(&E2, &nu2);
IsoElasticySchearCoeff mu2(&E2, &nu2);
// Set density coefficients for material 1 and 2
ConstantCoefficient dens1_coef(0.5);
ConstantCoefficient dens2_coef(1.0);
// Set damping coefficients
ProductCoefficient cm1_coef(0.02, dens1_coef);
ProductCoefficient cm2_coef(0.02, dens2_coef);
ProductCoefficient cl1_coef(0.01, lambda1);
ProductCoefficient cmu1_coef(0.01, mu1);
ProductCoefficient cl2_coef(0.01, lambda2);
ProductCoefficient cmu2_coef(0.01, mu2);
lin_elasticity_op.SetElasticityCoefficients(lambda1, mu1, lambda2, mu2);
lin_elasticity_op.SetDensityMaterialCoefficients(dens1_coef, dens2_coef);
lin_elasticity_op.SetDampingMaterialCoefficients(cm1_coef, cm2_coef);
lin_elasticity_op.SetDampingMaterialCoefficients(cl1_coef, cmu1_coef,
cl2_coef, cmu2_coef);
lin_elasticity_op.SetDensity(rho_coef);
//set bottom bdr to zero (both the velocities and the displacements)
lin_elasticity_op.SetZeroBdr(1);
lin_elasticity_op.SetVolForce(1.0 /*period*/, 1.0 /*amplitude*/, 0.2 /*radius*/,
0.0 /*x center*/ , 0.0 /*y center*/, 0.0 /*z center*/,
5.0 /* train length*/, 2.5 /*center of the train*/, 2.0 /*power*/);
lin_elasticity_op.AssembleExplicit();
// test mult explicit
{
BlockVector tst; tst.Update(lin_elasticity_op.GetTrueBlockOffsets());
tst=0.0; //tst.Randomize();
tst.UseDevice(true); tst.Read();
BlockVector grd; grd.Update(lin_elasticity_op.GetTrueBlockOffsets());
grd=0.0;
lin_elasticity_op.Mult(tst,grd);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(grd.GetBlock(1));
}
ParaViewDataCollection paraview_dc("isoel", &pmesh);
paraview_dc.SetPrefixPath("ParaView");
paraview_dc.SetLevelsOfDetail(order);
paraview_dc.SetDataFormat(VTKFormat::BINARY);
paraview_dc.SetHighOrderOutput(true);
paraview_dc.RegisterField("disp", &(lin_elasticity_op.GetDisplacement()));
paraview_dc.RegisterField("velo", &(lin_elasticity_op.GetVelocity()));
/*
{
int s=10;
StateSnapshotViewPacker packer(lin_elasticity_op.GetState().Size());
// storage stores StateSnapshotView snapshots using fixed-size slots
using Storage=mfem::FixedSlotMemoryCheckpointStorage<StateSnapshotView, StateSnapshotViewPacker>;
Storage storage(s, packer);
auto make_snapshot = [&](const State &u) -> StateSnapshotView
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
// Ensure host access if MFEM device is in use:
const mfem::real_t *vh = u.v.HostRead();
StateSnapshotView snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v_bytes = reinterpret_cast<const unsigned char*>(vh);
return snap;
};
auto restore_snapshot = [&](const StateSnapshotView &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
mfem::real_t *vh = u_out.v.HostWrite();
std::memcpy(vh,
snap.v_bytes,
(std::size_t)n * sizeof(mfem::real_t));
};
using Step = mfem::DynamicCheckpointing<StateSnapshotView, Storage>::Step;
State u;
u.v.SetSize(lin_elasticity_op.GetState().Size());
auto primal_step = [&](StateCheckPoint &u, Step i)
{
if (Mpi::Root()){
std::cout<<"Primal step: time= "<<u.time<<" obj= "<<u.obj;
}
const double dt = 0.01;
u.obj=dt*i;
u.time=dt*i;
u.v=(mfem::real_t)i;
if (Mpi::Root()){
std::cout<<" out Step: "<<i<<" time="<<u.time<<" obj="<<u.obj<<std::endl;
}
};
auto adjoint_step = [&](AdjState &lambda, const StateCheckPoint &u_i, Step i)
{
const double dt = 0.01;
MFEM_ASSERT(lambda.adj.Size() == u_i.state.Size(), "lambda and u_i size mismatch.");
if (Mpi::Root()){
std::cout<<"Adj step: time= "<<u_i.time<<" obj= "<<u_i.obj;
std::cout<<" adj time= "<<lambda.time<<" adj obj="<<lambda.obj<<std::endl;
}
lambda.obj=-u_i.obj;
lambda.time=lambda.time-dt;
};
// Initial condition
StateCheckPoint spt; spt.obj=-1.0; spt.time=-1.0; spt.state=(lin_elasticity_op.GetState());
Step i=0;
mfem::real_t t=0.0;
mfem::real_t dt=0.01;
while(t<0.2)
{
ckpt.ForwardStep(i, spt, primal_step, make_snapshot);
t=t+dt;
++i;
}
const Step m = i;
if (Mpi::Root()){
std::cout<<" Total number of steps="<<m<<std::endl;
}
AdjState ast; ast.obj=1.0; ast.time=spt.time;
ast.adj=(lin_elasticity_op.GetState());
ast.grd=(lin_elasticity_op.GetState());
for (Step j = m - 1; j >= 0; --j)
{
if (Mpi::Root()){
std::cout<<" Outer steps="<<j<<std::endl;
}
ckpt.BackwardStep(j, ast, spt, primal_step, adjoint_step, make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
}
*/
//Test Jacobian transpose
{
BlockVector x; x.Update(lin_elasticity_op.GetTrueBlockOffsets());
BlockVector w; w.Update(lin_elasticity_op.GetTrueBlockOffsets());
BlockVector d; d.Update(lin_elasticity_op.GetTrueBlockOffsets());
BlockVector g; g.Update(lin_elasticity_op.GetTrueBlockOffsets());
x.Randomize();
real_t t = 0.0;
ExampleObjectiveIntegrand* obj=new ExampleObjectiveIntegrand(lin_elasticity_op.GetFESpace());
lin_elasticity_op.Mult(x,d);
real_t oo=obj->EvalScalar(d);
if(Mpi::Root())
{
std::cout<<"t="<<t<<" oo="<<oo<<std::endl;
}
obj->EvalGradient(d,w);
lin_elasticity_op.JacobianMultTranspose(x,w,g); //the gradient is in g
//FD check
{
BlockVector rnd; rnd.Update(lin_elasticity_op.GetTrueBlockOffsets());
rnd.Randomize();
real_t sca=1.0;
real_t ipr=mfem::InnerProduct(pmesh.GetComm(),rnd,g);
for(int i=0;i<20;i++){
w.Set(sca,rnd);
w.Add(1.0,x);
lin_elasticity_op.Mult(w,d);
real_t co=obj->EvalScalar(d);
w.Set(-sca,rnd);
w.Add(1.0,x);
lin_elasticity_op.Mult(w,d);
real_t mo=obj->EvalScalar(d);
if(Mpi::Root())
{
std::cout<<"s="<<sca<<" o="<<oo<<" c="<<co<<" gr="
<<ipr<<" fd="<<(co-oo)/sca
<<" cd="<<(co-mo)/(2.0*sca)<<std::endl;
}
sca=sca/2;
}
}
delete obj;
}
//test time integration
/*
{
real_t t = 0.0;
BlockVector tsol; tsol.Update(lin_elasticity_op.GetTrueBlockOffsets());
//set initial conditions at time t
tsol=0.0;
ExampleObjectiveIntegrand* obj=new ExampleObjectiveIntegrand(lin_elasticity_op.GetFESpace());
real_t oo=obj->EvalScalar(tsol);
if(Mpi::Root())
{
std::cout<<"t="<<t<<" oo="<<oo<<std::endl;
}
delete obj;
// 4. Define the ODE solver used for time integration. Several explicit
// Runge-Kutta methods are available.
unique_ptr<ODESolver> ode_solver = ODESolver::Select(ode_solver_type);
lin_elasticity_op.SetTime(t);
ode_solver->Init(lin_elasticity_op);
paraview_dc.SetCycle(0);
paraview_dc.SetTime(0.0);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(tsol.GetBlock(1));
lin_elasticity_op.GetDisplacement().SetFromTrueDofs(tsol.GetBlock(0));
paraview_dc.Save();
real_t dt_real = 0.005;
//ode_solver->Run(tsol, t, dt_real, 1.0);
for(int i=0;i<2000; i++){
ode_solver->Step(tsol, t, dt_real);
if (Mpi::Root())
{
std::cout << "t: " << t << std::endl;
}
if((i%5)==0){
paraview_dc.SetCycle(i+1);
paraview_dc.SetTime(t);
lin_elasticity_op.GetVelocity().SetFromTrueDofs(tsol.GetBlock(1));
lin_elasticity_op.GetDisplacement().SetFromTrueDofs(tsol.GetBlock(0));
paraview_dc.Save();
}
}
obj=new ExampleObjectiveIntegrand(lin_elasticity_op.GetFESpace());
oo=obj->EvalScalar(tsol);
if(Mpi::Root())
{
std::cout<<"t="<<t<<" oo="<<oo<<std::endl;
}
delete obj;
}
*/
/*
if (paraview)
{
ParaViewDataCollection paraview_dc("isoel", &pmesh);
paraview_dc.SetPrefixPath("ParaView");
paraview_dc.SetLevelsOfDetail(order);
paraview_dc.SetDataFormat(VTKFormat::BINARY);
paraview_dc.SetHighOrderOutput(true);
paraview_dc.SetCycle(0);
paraview_dc.SetTime(0.0);
//paraview_dc.RegisterField("disp", &sol);
paraview_dc.RegisterField("disp", &(lin_elasticity_op.GetDisplacement()));
paraview_dc.RegisterField("velo", &(lin_elasticity_op.GetVelocity()));
paraview_dc.Save();
}
*/
return EXIT_SUCCESS;
}
+538
View File
@@ -0,0 +1,538 @@
#include "mfem.hpp"
class InterpCoeff: public mfem::Coefficient
{
public:
InterpCoeff(std::shared_ptr<mfem::Coefficient> c1_,
std::shared_ptr<mfem::Coefficient> c2_,
std::shared_ptr<mfem::Coefficient> rho_)
: c1(c1_), c2(c2_), rho(rho_)
{
}
virtual mfem::real_t Eval (mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip)
{
mfem::real_t v1=c1->Eval(T,ip);
mfem::real_t v2=c2->Eval(T,ip);
mfem::real_t r=rho->Eval(T,ip);
return v1*r+v2*(1.0-r);
}
private:
std::shared_ptr<mfem::Coefficient> c1;
std::shared_ptr<mfem::Coefficient> c2;
std::shared_ptr<mfem::Coefficient> rho;
};
namespace mfem{
class PAExplicitDiffusionOperator : public TimeDependentOperator
{
private:
ParFiniteElementSpace &pfes;
Array<int> ess_tdof_list;
// Partially assembled bilinear forms (no assembled sparse matrices)
ParBilinearForm k_form; // diffusion stiffness
// Minv(i) = 1 / diag(M)(i) on TRUE dofs
Vector Minv;
mutable Vector tmpv;
public:
/// ess_bdr: boundary attribute marker (size = pmesh->bdr_attributes.Max()), 1 -> essential (Dirichlet)
/// kappa: diffusivity coefficient
PAExplicitDiffusionOperator(ParFiniteElementSpace &pfes_,
const Array<int> &ess_tdof_list_,
Coefficient &kappa)
: TimeDependentOperator(pfes_.GetTrueVSize(), 0.0, TimeDependentOperator::EXPLICIT),
pfes(pfes_),
k_form(&pfes_),
Minv(height),
tmpv(height)
{
// Essential TRUE dofs
// pfes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
ess_tdof_list=ess_tdof_list_;
// --- Mass operator (PA) ---
auto mifi=new MassIntegrator();
int order=pfes.GetOrder(0);
IntegrationRules gll_rules(0, Quadrature1D::GaussLobatto);
const IntegrationRule &ir_ni = gll_rules.Get(pfes.GetParMesh()->GetTypicalElementGeometry(),
2 * order - 1);
mifi->SetIntRule(&ir_ni);
ParBilinearForm m_form(&pfes); // mass
m_form.SetAssemblyLevel(AssemblyLevel::PARTIAL);
m_form.AddDomainIntegrator(mifi);
m_form.Assemble();
// Diagonal on TRUE dofs (works with PA in ParBilinearForm)
Vector Mdiag(height);
m_form.AssembleDiagonal(Mdiag);
Minv = Mdiag;
Minv.Reciprocal();
// --- Diffusion operator (PA) ---
k_form.SetAssemblyLevel(AssemblyLevel::PARTIAL);
k_form.AddDomainIntegrator(new DiffusionIntegrator(kappa));
k_form.Assemble();
}
/// Compute du_dt = -Minv .* (K u)
void Mult(const Vector &u, Vector &du_dt) const override
{
du_dt = 0.0;
// du_dt += -1 * (P^T K_local P) u (true-dof action; PA supported)
k_form.TrueAddMult(u, du_dt, -1.0);
// Apply diagonal inverse mass
du_dt *= Minv;
// Strongly enforce Dirichlet: derivative is zero on essential tdofs
if (ess_tdof_list.Size())
{
du_dt.SetSubVector(ess_tdof_list, 0.0);
}
}
const Array<int> &GetEssentialTrueDofs() const { return ess_tdof_list; }
/// y = (df/dx(x,t))^T * w
void JacobianMultTranspose(const Vector &x,
const Vector &w,
Vector &y) const override
{
y=0.0;
tmpv.Set(1.0,w);
if (ess_tdof_list.Size()){
tmpv.SetSubVector(ess_tdof_list, 0.0);}
// Apply diagonal inverse mass
tmpv*=Minv;
// y += -1 * (P^T K_local P) u (true-dof action; PA supported)
k_form.TrueAddMult(tmpv, y, -1.0);
}
};
};
class DiffusionTDOP:public mfem::TimeDependentOperator
{
public:
DiffusionTDOP(mfem::ParMesh* mesh_, int vorder =1, int dorder=1):order(vorder)
{
mesh=mesh_;
int dim=mesh->Dimension();
fec.reset(new mfem::H1_FECollection(vorder,dim));
fes.reset(new mfem::ParFiniteElementSpace(mesh,fec.get()));
dfec.reset(new mfem::H1_FECollection(dorder,dim));
dfes.reset(new mfem::ParFiniteElementSpace(mesh,dfec.get()));
gfdens.SetSpace(dfes.get());
//the state vector of the TDOP consists of [solution, density, obj]
siz_u=fes->GetTrueVSize(); //solution
siz_d=dfes->GetTrueVSize(); //design
siz_f=1; //objective;
block_true_offsets.SetSize(4);
block_true_offsets[0] = 0;
block_true_offsets[1] = siz_u;
block_true_offsets[2] = siz_d;
block_true_offsets[3] = siz_f;
block_true_offsets.PartialSum();
//set the width and the height of the operator
this->width= block_true_offsets[3];
this->height= block_true_offsets[3];
}
const mfem::ParFiniteElementSpace* GetStateFES(){ return fes.get(); }
const mfem::ParFiniteElementSpace* GetDesignFEM(){ return dfes.get(); }
virtual
~DiffusionTDOP()
{
}
void SetLoad(int ind, std::shared_ptr<mfem::Coefficient> l)
{
load[ind]=l;
}
void SetZeroBC(int ind)
{
zero_bc.insert(ind);
}
// free the allocated bilinear forms, matrices and solvers
void Reset()
{
}
/// Asembles the load, the mass and the stiffness matrices for
/// a give true vector tvdens with densities varying from 0 to 1
/// and a given time t.
void Assemble(const mfem::Vector& tvdens, mfem::real_t t)
{
gfdens.SetFromTrueDofs(tvdens);
gfc.reset(new mfem::GridFunctionCoefficient(&gfdens));
if(nullptr==kbf.get())
{
// set constrained dofs
SetEssTDofs(ess_tdofv);
//allocate the system matrices and solvers
//allocate diagonal mass matrix and its inverse
{
mfem::IntegrationRules gll_rules(0, mfem::Quadrature1D::GaussLobatto);
const mfem::IntegrationRule &ir_ni = gll_rules.Get(mesh->GetTypicalElementGeometry(),
2 * order - 1);
mfem::ParBilinearForm mform(fes.get());
mform.SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
cs.reset(new InterpCoeff(cs1,cs2,gfc));
auto *mblfi = new mfem::MassIntegrator(*cs);
mblfi->SetIntRule(&ir_ni);
mform.AddDomainIntegrator(mblfi);
mfem::Vector diag(fes->GetTrueVSize());
mform.AssembleDiagonal(diag);
minv=diag;
minv.Reciprocal();
// Ensure essential dofs stay fixed: zero inverse there (extra safety)
if (ess_tdofv.Size())
{
minv.SetSubVector(ess_tdofv, 0.0);
}
}
//allocate the stiffness matrix
{
kap.reset(new InterpCoeff(kap1,kap2,gfc));
kbf.reset(new mfem::ParBilinearForm(fes.get()));
kbf->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
kbf->AddDomainIntegrator(new mfem::DiffusionIntegrator(*kap));
kbf->Assemble();
}
}
//allocate the RHS
}
virtual
void Mult(const mfem::Vector& x, mfem::Vector& y) const override
{
mfem::BlockVector bx(const_cast<mfem::Vector&>(x), block_true_offsets);
mfem::BlockVector by(y, block_true_offsets);
//compute the time derivatives
//compute the time derivative of the objective
}
void SetEssTDofs(mfem::Array<int>& ess_dofs)
{
ess_dofs.DeleteAll();
mfem::Array<int> ess_bdr(mesh->bdr_attributes.Max());
ess_bdr=0;
for (auto it=zero_bc.begin(); it!=zero_bc.end(); ++it)
{
int attr = *it;
ess_bdr[attr-1] = 1;
}
fes->GetEssentialTrueDofs(ess_bdr,ess_dofs);
}
/// Sets Dirichlet dofs to zero
void SetEssTDofs(mfem::Vector& v) const
{
mfem::Array<int> loc_tdofs;
mfem::Array<int> ess_bdr(mesh->bdr_attributes.Max());
ess_bdr=0;
for (auto it=zero_bc.begin(); it!=zero_bc.end(); ++it)
{
int attr = *it;
ess_bdr[attr-1] = 1;
}
fes->GetEssentialTrueDofs(ess_bdr, loc_tdofs);
for (int j=0; j<loc_tdofs.Size(); j++)
{
v[loc_tdofs[j]]=0.0;
}
}
private:
int order;
mfem::ParMesh* mesh;
std::unique_ptr<mfem::FiniteElementCollection> fec;
std::unique_ptr<mfem::ParFiniteElementSpace> fes;
std::unique_ptr<mfem::FiniteElementCollection> dfec;
std::unique_ptr<mfem::ParFiniteElementSpace> dfes;
std::unique_ptr<mfem::HypreBoomerAMG> prec;
std::unique_ptr<mfem::CGSolver> ls;
std::shared_ptr<mfem::Coefficient> kap1, kap2;
std::shared_ptr<mfem::Coefficient> cs1, cs2;
std::shared_ptr<InterpCoeff> cs,kap;
std::shared_ptr<mfem::GridFunctionCoefficient> gfc;
mfem::ParGridFunction gfdens;
std::map<int, std::shared_ptr<mfem::Coefficient>> load;
std::set<int> zero_bc;
// holds the constrained DOFs
mfem::Array<int> ess_tdofv;
mfem::Array<int> block_true_offsets;
int siz_u;
int siz_d;
int siz_f;
std::unique_ptr<mfem::ParBilinearForm> kbf;
mfem::Vector minv;
};
using namespace std;
using namespace mfem;
constexpr auto MESH_TRI = MFEM_SOURCE_DIR "/miniapps/mtop/sq_2D_9_tri.mesh";
constexpr auto MESH_QUAD = MFEM_SOURCE_DIR "/miniapps/mtop/sq_2D_9_quad.mesh";
int main(int argc, char *argv[])
{
// Initialize MPI and HYPRE.
Mpi::Init();
Hypre::Init();
// Parse command-line options.
const char *mesh_file = MESH_QUAD;
const char *device_config = "cpu";
int order = 2;
bool pa = false;
bool dfem = false;
bool mesh_tri = false;
bool mesh_quad = false;
int par_ref_levels = 1;
bool paraview = false;
bool visualization = true;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree) or -1 for"
" isoparametric space.");
args.AddOption(&pa, "-pa", "--partial-assembly", "-no-pa",
"--no-partial-assembly", "Enable Partial Assembly.");
args.AddOption(&dfem, "-dfem", "--dFEM", "-no-dfem", "--no-dFEM",
"Enable or not dFEM.");
args.AddOption(&mesh_tri, "-tri", "--triangular", "-no-tri",
"--no-triangular", "Enable or not triangular mesh.");
args.AddOption(&mesh_quad, "-quad", "--quadrilateral", "-no-quad",
"--no-quadrilateral", "Enable or not quadrilateral mesh.");
args.AddOption(&par_ref_levels, "-prl", "--par-ref-levels",
"Number of times to refine the mesh uniformly in parallel.");
args.AddOption(&paraview, "-pv", "--paraview", "-no-pv", "--no-paraview",
"Enable or not Paraview visualization");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.ParseCheck();
MFEM_VERIFY(!(pa && dfem), "pa and dfem cannot be both set");
// Enable hardware devices such as GPUs, and programming models such as
// CUDA, OCCA, RAJA and OpenMP based on command line options.
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// Read the (serial) mesh from the given mesh file on all processors. We
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
// and volume meshes with the same code.
Mesh mesh(mesh_tri ? MESH_TRI : mesh_quad ? MESH_QUAD : mesh_file, 1, 1);
const int dim = mesh.Dimension();
// Refine the serial mesh on all processors to increase the resolution. In
// this example we do 'ref_levels' of uniform refinement. We choose
// 'ref_levels' to be the largest number that gives a final mesh with no
// more than 1000 elements.
{
const int ref_levels =
(int)floor(log(1000. / mesh.GetNE()) / log(2.) / dim);
for (int l = 0; l < ref_levels; l++) { mesh.UniformRefinement(); }
}
if (Mpi::Root())
{
std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
}
// Define a parallel mesh by a partitioning of the serial mesh. Refine
// this mesh further in parallel to increase the resolution. Once the
// parallel mesh is defined, the serial mesh can be deleted.
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
for (int l = 0; l < par_ref_levels; l++) { pmesh.UniformRefinement(); }
mfem::H1_FECollection fec(order, pmesh.Dimension());
mfem::ParFiniteElementSpace pfes(&pmesh, &fec);
mfem::Array<int> ess_tdof_list;
{
// Dirichlet boundary marker (example: all boundary attributes)
mfem::Array<int> ess_bdr(pmesh.bdr_attributes.Max());
ess_bdr = 0;
ess_bdr[9]=1.0;
pfes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
}
mfem::ConstantCoefficient kappa(1.0);
mfem::PAExplicitDiffusionOperator oper(pfes, ess_tdof_list, kappa);
// Initial condition as a ParGridFunction
mfem::ParGridFunction u_gf(&pfes);
u_gf = 0.0;
// replace with projection
{
mfem::FunctionCoefficient fc([](const Vector &x) -> real_t
{ return std::sin(4.0*x[0]*M_PI)*std::sin(4.0*x[1]*M_PI); });
//project
u_gf.ProjectCoefficient(fc);
}
// True dof vector state
mfem::Vector u;
u_gf.GetTrueDofs(u);
// Enforce homogeneous Dirichlet initially
u.SetSubVector(oper.GetEssentialTrueDofs(), 0.0);
u_gf.SetFromTrueDofs(u);
// set paraview output
ParaViewDataCollection paraview_dc("tdiff", &pmesh);
paraview_dc.SetPrefixPath("ParaView");
paraview_dc.SetLevelsOfDetail(order);
paraview_dc.SetDataFormat(VTKFormat::BINARY);
paraview_dc.SetHighOrderOutput(true);
paraview_dc.RegisterField("temp", &u_gf);
// Pick an explicit solver
mfem::RK4Solver ode;
ode.Init(oper);
double t = 0.0;
double dt = 1e-5; // diffusion CFL ~ O(h^2); must be small
paraview_dc.SetCycle(0);
paraview_dc.SetTime(t);
paraview_dc.Save();
/*
for (int ti = 0; ti < 10; ti++)
{
ode.Step(u, t, dt);
// Keep Dirichlet dofs pinned (avoid drift)
u.SetSubVector(oper.GetEssentialTrueDofs(), 0.0);
u_gf.SetFromTrueDofs(u);
paraview_dc.SetCycle(ti+1);
paraview_dc.SetTime(t);
paraview_dc.Save();
}
*/
//test the RK4 adjoint
{
Vector x;
{
mfem::FunctionCoefficient fc([](const Vector &x) -> real_t
{ return std::sin(4.0*x[0]*M_PI)*std::sin(4.0*x[1]*M_PI); });
//project
u_gf.ProjectCoefficient(fc);
}
u_gf.GetTrueDofs(x);
// Enforce homogeneous Dirichlet initially
x.SetSubVector(oper.GetEssentialTrueDofs(), 0.0);
u_gf.SetFromTrueDofs(x);
u.Set(1.0,x);
Vector vone(x); vone=1.0;
//compute one step
t=0.0;
ode.Step(x, t, dt);
//compute objective
real_t obj=mfem::InnerProduct(pmesh.GetComm(),x,vone);
//compute the adjoint
mfem::Vector lam(u.Size()); lam=0.0;
{
ode.EnableAdjoint(mfem::ODESolver::AdjointMode::Discrete);
ode.SetSolution(u,0.0); //set the solution at t=0.0;
lam.Set(1.0,vone);
ode.AdjointStep(lam,t,dt); //on exit lam is the adjoint at t-dt
}
mfem::Vector rnd(u.Size()); rnd.Randomize();
rnd.SetSubVector(oper.GetEssentialTrueDofs(), 0.0);
real_t iprod=mfem::InnerProduct(pmesh.GetComm(),rnd,lam);
real_t sca=1.0;
for(int i=0;i<20;i++){
x.Set(sca,rnd);
x.Add(1.0,u);
t=0.0;
ode.Step(x, t, dt);
real_t pobj=mfem::InnerProduct(pmesh.GetComm(),x,vone);
x.Set(-sca,rnd);
x.Add(1.0,u);
t=0.0;
ode.Step(x, t, dt);
real_t mobj=mfem::InnerProduct(pmesh.GetComm(),x,vone);
if(mfem::Mpi::Root())
{
std::cout<<" scale="<<sca<<" "<<" o="<<obj<<" p="<<pobj
<<" do="<<(pobj-mobj)/(2.0*sca)
<<" oo="<<(obj-mobj)/sca
<<" to="<<iprod<<std::endl;
}
sca=sca/10.0;
}
}
return EXIT_SUCCESS;
}
+2
View File
@@ -39,6 +39,8 @@ set(UNIT_TESTS_SRCS
dfem/test_divergence.cpp
dfem/test_lvector_interface.cpp
dfem/test_mass.cpp
dfem/test_transpose.cpp
dfem/test_multiple_integrators.cpp
general/test_array.cpp
general/test_scan.cpp
general/test_arrays_by_name.cpp
+1 -1
View File
@@ -218,7 +218,7 @@ template <int DIM> void mass_mat_mixed(const char* filename, int p)
SECTION("spmat")
{
SparseMatrix *A;
SparseMatrix *A = nullptr;
ddopdu->Assemble(A);
TestSameMatrices(*A, blf.SpMat());
delete A;
@@ -0,0 +1,202 @@
// 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.
#include "../unit_tests.hpp"
#include "../linalg/test_same_matrices.hpp"
#include "mfem.hpp"
#include "fem/dfem/doperator.hpp"
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
template <int DIM>
void mult_integ(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
ParGridFunction x(&fes), y(&fes), z(&fes);
Vector X(fes.GetTrueVSize()), Y(fes.GetTrueVSize()), Z(fes.GetTrueVSize());
X.Randomize(1);
x.SetFromTrueDofs(X);
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new DiffusionIntegrator(ir));
blf.AddDomainIntegrator(new MassIntegrator(ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, Y);
const auto mass_qf =
[] MFEM_HOST_DEVICE(
const dscalar_t &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
return tuple{u * w * det(J)};
};
const auto diffusion_qf =
[] MFEM_HOST_DEVICE(
const tensor<dscalar_t, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
return tuple{(dudxi * inv(J)) * transpose(inv(J)) * w * det(J)};
};
static constexpr int U = 0, Coords = 1;
const auto sol = std::vector{ FieldDescriptor{ U, &fes } };
DifferentiableOperator dop(sol, {{Coords, nodes->ParFESpace()}}, pmesh);
auto derivatives = std::integer_sequence<size_t, U> {};
dop.AddDomainIntegrator(diffusion_qf,
tuple{ Gradient<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} },
*ir, all_domain_attr, derivatives);
dop.AddDomainIntegrator(mass_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<U>{} },
*ir, all_domain_attr, derivatives);
SECTION("action")
{
dop.SetParameters({ nodes });
fes.GetRestrictionMatrix()->Mult(x, X);
dop.Mult(X, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("linearized action")
{
auto ddopdu = dop.GetDerivative(U, {&x}, {nodes});
fes.GetRestrictionMatrix()->Mult(x, X);
ddopdu->Mult(X, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("linearized assembled SparseMatrix")
{
auto ddopdu = dop.GetDerivative(U, {&x}, {nodes});
SparseMatrix *A = nullptr;
ddopdu->Assemble(A);
A->Mult(x, z);
fes.GetProlongationMatrix()->MultTranspose(z, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
delete A;
}
SECTION("linearized assembled HypreParMatrix")
{
auto ddopdu = dop.GetDerivative(U, {&x}, {nodes});
HypreParMatrix *A = nullptr;
ddopdu->Assemble(A);
fes.GetRestrictionMatrix()->Mult(x, X);
A->Mult(X, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
delete A;
}
}
// no GPU tag to avoid failing 'hypre parallel mat' section
TEST_CASE("dFEM Multiple Integrators", "[Parallel][dFEM][XXX]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
SECTION("2d")
{
const auto filename2d =
GENERATE(
"../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh"
);
mult_integ<2>(filename2d, p);
}
SECTION("3d")
{
const auto filename3d =
GENERATE(
"../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
);
mult_integ<3>(filename3d, p);
}
}
#endif // MFEM_USE_MPI
+409
View File
@@ -0,0 +1,409 @@
// 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.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#include "fem/dfem/doperator.hpp" // TODO: remove before merge
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
template <int DIM>
void transpose(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh serial_mesh(filename);
ParMesh mesh(MPI_COMM_WORLD, serial_mesh);
serial_mesh.Clear();
mesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(mesh.GetNodes());
p = std::max(p, mesh.GetNodalFESpace()->GetMaxElementOrder());
Array<int> all_domain_attr;
if (mesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(mesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace scalar_fes(&mesh, &fec);
ParFiniteElementSpace vector_fes(&mesh, &fec, DIM);
ParGridFunction sgf(&scalar_fes);
auto f0 = [](const Vector &x)
{
if constexpr (DIM == 3)
{
return M_PI*cos(M_PI*x[0]) * sin(M_PI*x[1]) * sin(M_PI*x[2]);
}
return M_PI*cos(M_PI*x[0]) * sin(M_PI*x[1]);
};
FunctionCoefficient f0_coeff(f0);
sgf.ProjectCoefficient(f0_coeff);
ParGridFunction vgf(&vector_fes);
auto gradf1 = [](const Vector &x, Vector &u)
{
if constexpr (DIM == 3)
{
u(0) = M_PI*cos(M_PI*x[0]) * sin(M_PI*x[1]) * sin(M_PI*x[2]);
u(1) = M_PI*sin(M_PI*x[0]) * cos(M_PI*x[1]) * sin(M_PI*x[2]);
u(2) = M_PI*sin(M_PI*x[0]) * sin(M_PI*x[1]) * cos(M_PI*x[2]);
return;
}
u(0) = M_PI*cos(M_PI*x[0]) * sin(M_PI*x[1]);
u(1) = M_PI*sin(M_PI*x[0]) * cos(M_PI*x[1]);
};
VectorFunctionCoefficient gradf1_coeff(DIM, gradf1);
vgf.ProjectCoefficient(gradf1_coeff);
const auto* ir = &IntRules.Get(mesh.GetTypicalElementGeometry(), 2 * p);
SECTION("Mass Transpose Action")
{
ParBilinearForm Mblf(&scalar_fes);
auto mass_integ = new MassIntegrator;
mass_integ->SetIntegrationRule(*ir);
Mblf.AddDomainIntegrator(mass_integ);
Mblf.Assemble();
Mblf.Finalize();
auto Mmat = Mblf.ParallelAssemble();
static constexpr int SCALAR = 0, COORDINATES = 1;
const auto sol = std::vector{FieldDescriptor{SCALAR, &scalar_fes}};
const auto par = std::vector{FieldDescriptor{COORDINATES, nodes->ParFESpace()}};
DifferentiableOperator dop(sol, par, mesh);
const auto gradient_qf = [] MFEM_HOST_DEVICE(
const dscalar_t &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
return tuple{u * w * det(J)};
};
auto derivatives = std::integer_sequence<size_t, SCALAR> {};
dop.AddDomainIntegrator(gradient_qf,
tuple{Value<SCALAR>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<SCALAR>{}},
*ir, all_domain_attr, derivatives);
dop.SetParameters({nodes});
Vector S, T, U;
S.SetSize(scalar_fes.GetTrueVSize());
T.SetSize(scalar_fes.GetTrueVSize());
U.SetSize(scalar_fes.GetTrueVSize());
sgf.GetTrueDofs(S);
Mmat->MultTranspose(S, T);
auto ddop = dop.GetDerivative(SCALAR, {&sgf}, {nodes});
ddop->MultTranspose(S, U);
T -= U;
real_t norm_g, norm_l = T.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, mesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
delete Mmat;
}
SECTION("Vector Mass Transpose Action")
{
ParBilinearForm Mvblf(&vector_fes);
auto mass_integ = new VectorMassIntegrator;
mass_integ->SetIntegrationRule(*ir);
Mvblf.AddDomainIntegrator(mass_integ);
Mvblf.Assemble();
Mvblf.Finalize();
auto Mvmat = Mvblf.ParallelAssemble();
static constexpr int VECTOR = 0, COORDINATES = 1;
const auto sol = std::vector{FieldDescriptor{VECTOR, &vector_fes}};
const auto par = std::vector{FieldDescriptor{COORDINATES, nodes->ParFESpace()}};
DifferentiableOperator dop(sol, par, mesh);
const auto gradient_qf = [] MFEM_HOST_DEVICE(
const tensor<dscalar_t, DIM> &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
return tuple{u * w * det(J)};
};
auto derivatives = std::integer_sequence<size_t, VECTOR> {};
dop.AddDomainIntegrator(gradient_qf,
tuple{Value<VECTOR>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<VECTOR>{}},
*ir, all_domain_attr, derivatives);
dop.SetParameters({nodes});
Vector V, W, Z;
V.SetSize(vector_fes.GetTrueVSize());
W.SetSize(vector_fes.GetTrueVSize());
Z.SetSize(vector_fes.GetTrueVSize());
vgf.GetTrueDofs(V);
Mvmat->MultTranspose(V, W);
auto ddop = dop.GetDerivative(VECTOR, {&vgf}, {nodes});
ddop->MultTranspose(V, Z);
W -= Z;
real_t norm_g, norm_l = W.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, mesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
delete Mvmat;
}
SECTION("Discrete Gradient Transpose Action")
{
ParMixedBilinearForm Gblf(&scalar_fes, &vector_fes);
auto grad_integ = new GradientIntegrator;
grad_integ->SetIntegrationRule(*ir);
Gblf.AddDomainIntegrator(grad_integ);
Gblf.Assemble();
Gblf.Finalize();
auto Gmat = Gblf.ParallelAssemble();
static constexpr int SCALAR = 0, VECTOR = 2, COORDINATES = 1;
const auto sol = std::vector{FieldDescriptor{SCALAR, &scalar_fes}};
const auto par = std::vector
{
FieldDescriptor{VECTOR, &vector_fes},
FieldDescriptor{COORDINATES, nodes->ParFESpace()}
};
DifferentiableOperator dop(sol, par, mesh);
const auto gradient_qf = [] MFEM_HOST_DEVICE(
const tensor<dscalar_t, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
const auto dudx = dudxi * inv(J);
return tuple{dudx * w * det(J)};
};
auto derivatives = std::integer_sequence<size_t, SCALAR> {};
dop.AddDomainIntegrator(gradient_qf,
tuple{Gradient<SCALAR>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<VECTOR>{}},
*ir, all_domain_attr, derivatives);
dop.SetParameters({&vgf, nodes});
Vector S, T, V;
S.SetSize(scalar_fes.GetTrueVSize());
T.SetSize(scalar_fes.GetTrueVSize());
vgf.GetTrueDofs(V);
Gmat->MultTranspose(V, S);
auto ddop = dop.GetDerivative(SCALAR, {&sgf}, {&vgf, nodes});
ddop->MultTranspose(V, T);
S -= T;
real_t norm_g, norm_l = S.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, mesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
delete Gmat;
}
SECTION("Scalar Convection Transpose Action")
{
auto b_func = [](const Vector &x, Vector &b)
{
b(0) = cos(x[0] * 2.0 * M_PI);
b(1) = 1.0 + cos(x[1] * 2.0 * M_PI);
if constexpr (DIM == 3)
{
b(2) = 2.0 + cos(x[2] * 2.0 * M_PI);
}
};
VectorFunctionCoefficient b_coeff(DIM, b_func);
ParBilinearForm Gblf(&scalar_fes);
auto conv_integ = new ConvectionIntegrator(b_coeff);
conv_integ->SetIntegrationRule(*ir);
Gblf.AddDomainIntegrator(conv_integ);
Gblf.Assemble();
Gblf.Finalize();
auto Gmat = Gblf.ParallelAssemble();
static constexpr int SCALAR = 0, COORDINATES = 1;
const auto sol = std::vector{FieldDescriptor{SCALAR, &scalar_fes}};
const auto par = std::vector
{
FieldDescriptor{COORDINATES, nodes->ParFESpace()}
};
DifferentiableOperator dop(sol, par, mesh);
const auto convection_qf =
[] MFEM_HOST_DEVICE(
const tensor<dscalar_t, DIM> &dudxi,
const tensor<real_t, DIM> &x,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
const auto dudx = dudxi * inv(J);
tensor<dscalar_t, DIM> b{};
b(0) = cos(x[0] * 2.0 * M_PI);
b(1) = 1.0 + cos(x[1] * 2.0 * M_PI);
if constexpr (DIM == 3)
{
b(2) = 2.0 + cos(x[2] * 2.0 * M_PI);
}
return tuple{dot(b, dudx) * w * det(J)};
};
auto derivatives = std::integer_sequence<size_t, SCALAR> {};
dop.AddDomainIntegrator(convection_qf,
tuple{Gradient<SCALAR>{}, Value<COORDINATES>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<SCALAR>{}},
*ir, all_domain_attr, derivatives);
dop.SetParameters({nodes});
Vector S, T, U;
S.SetSize(scalar_fes.GetTrueVSize());
T.SetSize(scalar_fes.GetTrueVSize());
U.SetSize(scalar_fes.GetTrueVSize());
U.Randomize(1);
{
Gmat->MultTranspose(U, S);
auto ddop = dop.GetDerivative(SCALAR, {&sgf}, {nodes});
ddop->MultTranspose(U, T);
S -= T;
real_t norm_g, norm_l = S.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, mesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
}
delete Gmat;
}
SECTION("Nonlinear VectorConvection Transpose Action")
{
auto b_func = [](const Vector &x, Vector &b)
{
b(0) = cos(x[0]) * sin(x[0]) * x[1];
b(1) = cos(x[1]) * sin(x[1]) * x[0];
if constexpr (DIM == 3)
{
b(2) = cos(x[2]) * sin(x[2]) * x[0];
}
};
VectorFunctionCoefficient b_coeff(DIM, b_func);
ParGridFunction ugf(&vector_fes);
ugf.ProjectCoefficient(b_coeff);
Vector U(vector_fes.GetTrueVSize());
ugf.GetTrueDofs(U);
ParNonlinearForm nlf(&vector_fes);
const auto vcinteg = new VectorConvectionNLFIntegrator();
vcinteg->SetIntegrationRule(*ir);
nlf.AddDomainIntegrator(vcinteg);
HypreParMatrix &Nmat = dynamic_cast<HypreParMatrix&>(nlf.GetGradient(U));
static constexpr int VELOCITY = 0, COORDINATES = 1;
const auto sol = std::vector{FieldDescriptor{VELOCITY, &vector_fes}};
const auto par = std::vector
{
FieldDescriptor{COORDINATES, nodes->ParFESpace()}
};
DifferentiableOperator dop(sol, par, mesh);
const auto nlconvection_qf =
[] MFEM_HOST_DEVICE(
const tensor<dscalar_t, DIM> &u,
const tensor<dscalar_t, DIM, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
return tuple{dot(dudx, u) * w * det(J)};
};
auto derivatives = std::integer_sequence<size_t, VELOCITY> {};
dop.AddDomainIntegrator(nlconvection_qf,
tuple{Value<VELOCITY>{}, Gradient<VELOCITY>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<VELOCITY>{}},
*ir, all_domain_attr, derivatives);
dop.SetParameters({nodes});
auto ddop = dop.GetDerivative(VELOCITY, {&ugf}, {nodes});
Vector S(U.Size()), T(U.Size()), Se(vector_fes.GetVSize());
Nmat.MultTranspose(U, S);
ddop->MultTranspose(U, T);
S -= T;
real_t norm_g, norm_l = S.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, mesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
}
}
TEST_CASE("dFEM Transpose", "[Parallel][dFEM][XXX]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
SECTION("2d")
{
const auto filename2d =
GENERATE(
"../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh"
);
transpose<2>(filename2d, p);
}
SECTION("3d")
{
const auto filename3d =
GENERATE(
"../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh"
);
transpose<3>(filename3d, p);
}
}
#endif