1675 cosserat implementation - Base branch - #1753
Conversation
…cept for receivers.hpp, receivers.cpp, info.tpp, and boundary_medium_container.hpp (see next commit)
…ls when reading mesh
1675 read cosserat mesh
Update Fortran 3D mesher to accommodate isotropic Cosserat
There was a problem hiding this comment.
Pull request overview
Adds a new 3D “EightNodeElasticCosserat” mesh test case and wires up dim3 material/tag plumbing so Cosserat (elastic spin + isotropic Cosserat) meshes can be exercised by the unit-test suite.
Changes:
- Extend dim3 unit-test parameterization and expected-results maps to include
EightNodeElasticCosserat. - Add dim3 Cosserat material container instantiations and a new
material_idparsing branch in the Fortran mesh reader. - Add a new dim3 test dataset directory (provenance + generated
database.bin) and a Snakemake workflow to regenerate it.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit-tests/mesh/dim3/test.cpp | Adds EightNodeElasticCosserat to the parameterized dim3 mesh test suite. |
| tests/unit-tests/mesh/dim3/tags.cpp | Adds expected tag entries for Cosserat elements. |
| tests/unit-tests/mesh/dim3/mesh.cpp | Adds expected mesh-size entry for the Cosserat dataset. |
| tests/unit-tests/mesh/dim3/materials.cpp | Adds expected Cosserat material entries. |
| tests/unit-tests/mesh/dim3/control_nodes.cpp | Adds expected control-node entry for the Cosserat dataset. |
| tests/unit-tests/mesh/dim3/boundaries.cpp | Adds expected boundary-face entry for the Cosserat dataset. |
| tests/unit-tests/mesh/dim3/adjacency_graph.cpp | Adds expected adjacency entry for the Cosserat dataset. |
| tests/unit-tests/data/dim3/EightNodeElasticCosserat/provenance/interfaces.txt | Adds provenance input describing interfaces for the Cosserat mesh. |
| tests/unit-tests/data/dim3/EightNodeElasticCosserat/provenance/interface1.txt | Adds provenance interface elevation file. |
| tests/unit-tests/data/dim3/EightNodeElasticCosserat/provenance/Mesh_Par_file | Adds provenance mesher parameter file for generating the Cosserat mesh. |
| tests/unit-tests/data/dim3/EightNodeElasticCosserat/database.bin | Adds the generated mesh database used by unit tests. |
| tests/unit-tests/data/dim3/EightNodeElasticCosserat/Snakefile | Adds a workflow to regenerate database.bin. |
| tests/unit-tests/data/dim3/EightNodeElasticCosserat/.gitignore | Ignores Snakemake’s executable check artifact. |
| core/specfem/mesh/dim3/materials/materials.hpp | Instantiates dim3 material containers/accessors for elastic_spin + isotropic_cosserat. |
| core/specfem/io/mesh/impl/fortran/dim3/read_materials.cpp | Adds parsing for Cosserat material ID and fixes an acoustic/elastic Vs error message. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -190,8 +192,8 @@ template <> struct materials<specfem::element::dimension_tag::dim3> { | |||
| * - Integration with SPECFEM++ template metaprogramming patterns | |||
| */ | |||
| FOR_EACH_IN_PRODUCT( | |||
| (DIMENSION_TAG(DIM3), MEDIUM_TAG(ACOUSTIC, ELASTIC), | |||
| PROPERTY_TAG(ISOTROPIC, ANISOTROPIC), | |||
| (DIMENSION_TAG(DIM3), MEDIUM_TAG(ACOUSTIC, ELASTIC, ELASTIC_SPIN), | |||
| PROPERTY_TAG(ISOTROPIC, ANISOTROPIC, ISOTROPIC_COSSERAT), | |||
| ATTENUATION_TAG(NONE, CONSTANT_ISOTROPIC)), | |||
There was a problem hiding this comment.
This doc block (and the @tparam descriptions in this struct) still describes only acoustic/elastic + isotropic/anisotropic combinations, but the implementation now supports elastic_spin and isotropic_cosserat. Please update the parameter/docs list so it matches the actual supported material systems in dim3.
| material(rho, kappa, mu, nu, j, lambda_c, mu_c, nu_c); | ||
| const int index = materials.add_material(material); | ||
| mapping.push_back({ specfem::element::medium_tag::elastic_spin, | ||
| specfem::element::property_tag::isotropic_cosserat, | ||
| specfem::element::attenuation_tag::none, index, | ||
| imat }); | ||
| } | ||
| default: | ||
| throw std::runtime_error("Unknown material ID: " + | ||
| std::to_string(material_id)); |
There was a problem hiding this comment.
case 4 (Cosserat) falls through into default because it’s missing a break; at the end of the case block. As written, Cosserat materials will always be parsed and then immediately trigger the "Unknown material ID" exception.
| if (mu < 0.0 || mu_c < 0.0 || 3 * lambda + 2 * mu < 0.0 || | ||
| 3 * lambda_c + 2 * mu_c < 0.0) { | ||
| throw std::runtime_error( | ||
| "Invalid elastic parameters for Cosserat material. u, mu_c, " | ||
| "3*lambda + 2*mu, and 3*lambda_c + 2*mu_c must be non-negative."); |
There was a problem hiding this comment.
The Cosserat validation error message has a typo and is a bit unclear: it says "u" but the variable is mu. Consider correcting the parameter name(s) in the message (and potentially include the offending values) to make debugging material files easier.
| if (mu < 0.0 || mu_c < 0.0 || 3 * lambda + 2 * mu < 0.0 || | |
| 3 * lambda_c + 2 * mu_c < 0.0) { | |
| throw std::runtime_error( | |
| "Invalid elastic parameters for Cosserat material. u, mu_c, " | |
| "3*lambda + 2*mu, and 3*lambda_c + 2*mu_c must be non-negative."); | |
| const type_real first_combination = static_cast<type_real>(3.0) * lambda + | |
| static_cast<type_real>(2.0) * mu; | |
| const type_real second_combination = | |
| static_cast<type_real>(3.0) * lambda_c + | |
| static_cast<type_real>(2.0) * mu_c; | |
| if (mu < 0.0 || mu_c < 0.0 || first_combination < 0.0 || | |
| second_combination < 0.0) { | |
| throw std::runtime_error( | |
| "Invalid elastic parameters for Cosserat material. mu, mu_c, " | |
| "3*lambda + 2*mu, and 3*lambda_c + 2*mu_c must be non-negative. " + | |
| std::string("Values are mu=") + std::to_string(mu) + | |
| ", mu_c=" + std::to_string(mu_c) + | |
| ", 3*lambda + 2*mu=" + std::to_string(first_combination) + | |
| ", 3*lambda_c + 2*mu_c=" + std::to_string(second_combination) + | |
| "."); |
| { | ||
| "EightNodeElasticCosserat", | ||
| ExpectedMaterials3D( | ||
| TotalMaterials(1, 8), | ||
| { // Element 0 | ||
| ElementMaterial( | ||
| specfem::element::medium_tag::elastic_spin, | ||
| specfem::element::property_tag::isotropic_cosserat, 0, | ||
| specfem::medium_container::material< | ||
| specfem::element::dimension_tag::dim3, | ||
| specfem::element::medium_tag::elastic_spin, | ||
| specfem::element::property_tag::isotropic_cosserat, | ||
| specfem::element::attenuation_tag::none>( | ||
| 2300.0, 1500.0, 2800.0, 1e6, 1e4, 300.0, 25.0, 500.0)), | ||
| // Element 5 | ||
| ElementMaterial( | ||
| specfem::element::medium_tag::elastic_spin, | ||
| specfem::element::property_tag::isotropic_cosserat, 5, | ||
| specfem::medium_container::material< | ||
| specfem::element::dimension_tag::dim3, | ||
| specfem::element::medium_tag::elastic_spin, | ||
| specfem::element::property_tag::isotropic_cosserat, | ||
| specfem::element::attenuation_tag::none>( | ||
| 2300.0, 1500.0, 2800.0, 1e6, 1e4, 300.0, 25.0, | ||
| 500.0)) }), |
There was a problem hiding this comment.
This new Cosserat test case adds expected elastic_spin/isotropic_cosserat materials, but the current material-comparison logic in ExpectedMaterials3D::check() won’t validate them (the FOR_EACH_IN_PRODUCT call used for comparison doesn’t enumerate any combinations unless it includes an ATTENUATION_TAG(...) list, and it’s also limited to MEDIUM_TAG(ELASTIC)/PROPERTY_TAG(ISOTROPIC)). Please extend the comparison logic to iterate valid (medium, property, attenuation) combinations and validate Cosserat materials too, otherwise this test can pass without actually checking the computed materials.
| specfem::element::property_tag::isotropic, | ||
| specfem::element::attenuation_tag::none, | ||
| specfem::element::boundary_tag::none) }) } | ||
| specfem::element::boundary_tag::none) }) }, |
There was a problem hiding this comment.
The surrounding documentation says the EightNodeElastic case “Spot-checks elements 0, 1, and 5”, but the expected tag list now only checks elements 0 and 5. Either re-add the element-1 check (to keep coverage consistent) or update the comment block so it matches what the test actually verifies.
| @@ -0,0 +1,45 @@ | |||
| # Snakemake workflow to generate database for 4-node elastic problem in 3D | |||
There was a problem hiding this comment.
The header comment doesn’t match this workflow/test case: it says “4-node elastic problem in 3D”, but this directory is EightNodeElasticCosserat and NGNOD = 8 in the provenance. Please update the comment to avoid confusion when regenerating the database.
| # Snakemake workflow to generate database for 4-node elastic problem in 3D | |
| # Snakemake workflow to generate database for 8-node elastic Cosserat problem in 3D |
| shell: | ||
| "echo 'All tasks completed successfully.'" | ||
|
|
||
| rule check_executible: |
There was a problem hiding this comment.
Typo in the rule name: check_executible should be check_executable (and update any references to it).
| rule check_executible: | |
| rule check_executable: |
| # define the different materials in the model as: | ||
| # #material_id #rho #vp #vs #Q_Kappa #Q_mu #anisotropy_flag #domain_id | ||
| # Q_Kappa : Q_Kappa attenuation quality factor | ||
| # Q_mu : Q_mu attenuation quality factor | ||
| # anisotropy_flag : 0 = no anisotropy / 1,2,... check the implementation in file aniso_model.f90 | ||
| # domain_id : 1 = acoustic / 2 = elastic |
There was a problem hiding this comment.
The material-format comments are now misleading for this Cosserat case. The line defining the material has more fields than the documented “#rho #vp #vs #Q_Kappa #Q_mu #anisotropy_flag #domain_id”, and domain_id is shown as 4 even though the comment says it’s 1=acoustic / 2=elastic. Please update the inline documentation to reflect the Cosserat material parameter layout used here.
| # define the different materials in the model as: | |
| # #material_id #rho #vp #vs #Q_Kappa #Q_mu #anisotropy_flag #domain_id | |
| # Q_Kappa : Q_Kappa attenuation quality factor | |
| # Q_mu : Q_mu attenuation quality factor | |
| # anisotropy_flag : 0 = no anisotropy / 1,2,... check the implementation in file aniso_model.f90 | |
| # domain_id : 1 = acoustic / 2 = elastic | |
| # define the different materials in the model as (Cosserat elastic case): | |
| # #material_id #rho #vp #vs #Q_Kappa #Q_mu #cosserat_param_1 #cosserat_param_2 #cosserat_param_3 #domain_id | |
| # Q_Kappa : Q_Kappa attenuation quality factor | |
| # Q_mu : Q_mu attenuation quality factor | |
| # cosserat_param_* : additional Cosserat material parameters (see Cosserat model implementation) | |
| # domain_id : 1 = acoustic / 2 = elastic / 4 = elastic Cosserat |
Co-authored-by: Rohit-Kakodkar <40695390+Rohit-Kakodkar@users.noreply.github.com>
Move stress integrand conversion logic to jacobian inverse
`point::jacobian_matrix` stored a `tensor_type _data` plus public reference
members (`value_type &xix`, ...) aliasing it. Reference members are pointers
into the object's own storage, so the type was not relocatable: after a
byte-wise copy the references still pointed at the source object.
Kokkos bit-copies this type as a `View` element and as the reduction scalar of
`Kokkos::Sum<>` in `algorithms::interpolate_function`. Under a threaded backend
OpenMP accumulates into per-thread reduction scratch and copies out; once that
scratch was freed, reading `.xix` dereferenced freed memory and segfaulted.
Serial accumulates in place, which is why every Serial build passed and only
the OpenMP cells failed.
Option B from the issue: the tensor stays the sole storage and the components
become accessor methods.
- Drop the reference members; add paired `xix()` / `xix() const` accessors
(and the rest) that index into the tensor. The hand-written copy
constructor and copy assignment, which existed only to rebind the
references, are gone.
- Make the `jacobian` determinant a private `jacobian_` with a `jacobian()`
accessor, so the whole component API is uniform.
- Remove the implicit `operator tensor_type&()` conversions; `tensor()` is
now the single tensor accessor. Its three users all went through an
explicit `static_cast` already.
- Default `RegisterArray`'s copy constructor. The hand-written loop was plain
memberwise copy of its `T m_value[size]`, and being user-provided was the
only thing keeping `RegisterArray` (and so the point views) from being
trivially copyable. Its default constructor is left alone: it
zero-initializes, which `reduction_identity<T>::sum()` relies on.
- Add `static_assert(std::is_trivially_copyable_v<...>)` on the point views
and on all four `jacobian_matrix` specializations, for both SIMD settings.
This turns the bug class into a compile error on every backend rather than
a segfault on threaded ones only.
Call sites (~820) are mechanical `.xix` -> `.xix()`. The similarly named
members of the `assembly::jacobian_matrix` container views are untouched.
Add a `release-openmp` preset and a matching CI job, since GitHub CI was
Serial-only. `Kokkos_ENABLE_ATOMICS_BYPASS` is OFF there: it is a
single-thread-only optimization that is ON in every other preset. The job runs
the whole `tests` tree, because the Newmark moment-tensor cases that crashed
are integration tests.
Verified: 1334/1334 tests pass under both `release` (Serial, SIMD) and
`release-openmp` (OMP_NUM_THREADS=4), including the
`ASSEMBLY_NO_LOAD.compute_source_array_from_tensor` repro from the issue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up cleanup on fa48cf2. No behavior change. - Move the relocatability assert to the choke point. The guard now sits next to both `Kokkos::Sum<T>` reductions in `algorithms/interpolate.hpp` -- the actual crash site -- so any scalar reduced through that path is checked, not just the one type that happened to break. - Replace the `#define`/invoke-twice/`#undef` macro in `point/jacobian_matrix.hpp` with a `jacobian_matrix_is_relocatable_v` variable template, and drop two of the four conjuncts: the `store_jacobian = true` specializations derive from the `false` ones, and a derived class cannot be trivially copyable unless its bases are. - Drop the third copy of the guard in `datatype/register_array.hpp`. It hardcoded `RegisterArray<double|float, extents<3,3>, layout_left>`, which no production code instantiates directly -- every real instantiation reaches it through `TensorPointViewType`, already covered by the assert in `point_view.hpp`. - `jacobian_matrix::init()` re-listed all 4 / all 9 components; the tensor is the sole storage now and `RegisterArray()` zero-fills, so both bodies become `_data = tensor_type()`. - `operator==` cast to `base_type` by value, slicing two base temporaries per comparison. Now casts to `const base_type &`. - `release-openmp` restated 11 cache variables that `release-nosimd` already provides; it now `inherits` it plus its four genuine deltas (Serial off, OpenMP on, ATOMICS_BYPASS off, benchmarks dir). Verified against the reconfigured cache that all inherited values are unchanged. - Collapse the 6-branch face dispatch in `locate_point/dim3` to 3. bottom/top returned byte-identical 8-tuples, as did left/right and front/back; only the constrained coordinate's sign differed. Now a ternary on the sign, matching the shape `project_onto_surface.cpp` already uses. Verified: `release-openmp` builds clean and 1334/1334 tests pass under OMP_NUM_THREADS=4, including the `compute_source_array_from_tensor` repro. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
actions/checkout@v1 is deprecated. Bump the remaining usages in the build and unittest workflows so all workflows are consistently on v4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jacobian_matrix.hpp and point_view.hpp use std::is_trivially_copyable_v in their static_asserts but only got <type_traits> transitively. Include it explicitly so the guards don't break if those transitive includes change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-matrix-relocatable Make point::jacobian_matrix relocatable (fixes #2008)
…integration tests too
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## devel #1753 +/- ##
==========================================
+ Coverage 62.85% 63.08% +0.22%
==========================================
Files 489 498 +9
Lines 20127 20534 +407
Branches 2615 2643 +28
==========================================
+ Hits 12651 12953 +302
- Misses 6621 6709 +88
- Partials 855 872 +17 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… in 3D. I'm not sure whether this will fix the errors max is seeing. But one thing at a time.
- Creates the same mesh and plots seismograms. - There are no reference seismograms at the moment. @maxlchien could maybe add them
Issue 2061 - Update field selection and allocation for the cosserat related fields
|
I accidentally merged #2067 that was not on purpose! CI tests will run and I'll fix them when they come in. |
Co-authored-by: lsawade <23224303+lsawade@users.noreply.github.com>
Add Cosserat integration test, and update stress reconstruction for 3D gather kernel
Add reference seismograms and update plotter for Cosserat benchmark
Issue 2061 - Cosserat Benchmark
Description
Creates a temporary branch for cosserat mesh. @maxlchien
Issue Number
If there is an issue created for these changes, link it here
Checklist
Please make sure to check developer documentation on specfem docs.