diff --git a/cmake/AddInputFileTests.cmake b/cmake/AddInputFileTests.cmake index 790f26219928..2c72b0678407 100644 --- a/cmake/AddInputFileTests.cmake +++ b/cmake/AddInputFileTests.cmake @@ -277,6 +277,8 @@ configure_file( # These paths should be relative to the input file directory passed to # `add_input_file_tests` set(INPUT_FILE_WHITELIST - "PreprocessCceWorldtube/PreprocessCceWorldtube.yaml") + "PreprocessCceWorldtube/PreprocessCceWorldtube.yaml" + "PreprocessCceWorldtube/BssnPreprocessCceWorldtube.yaml" + "PreprocessCceWorldtube/Z4cPreprocessCceWorldtube.yaml") -add_input_file_tests("${CMAKE_SOURCE_DIR}/tests/InputFiles/" ${INPUT_FILE_WHITELIST}) +add_input_file_tests("${CMAKE_SOURCE_DIR}/tests/InputFiles/" "${INPUT_FILE_WHITELIST}") diff --git a/docs/References.bib b/docs/References.bib index f7e2b13feb5a..6bd1546496a6 100644 --- a/docs/References.bib +++ b/docs/References.bib @@ -1323,6 +1323,21 @@ @book{HesthavenWarburton publisher = "Springer", } +@article{Hilditch:2012fp, + author = "Hilditch, David and Bernuzzi, Sebastiano and Thierfelder, Marcus + and Cao, Zhoujian and Tichy, Wolfgang and Bruegmann, Bernd", + title = "{Compact binary evolutions with the Z4c formulation}", + eprint = "1212.2901", + archivePrefix = "arXiv", + primaryClass = "gr-qc", + doi = "10.1103/PhysRevD.88.084057", + url = "https://doi.org/10.1103/PhysRevD.88.084057", + journal = "Phys. Rev. D", + volume = "88", + pages = "084057", + year = "2013" +} + @article{Holst2004wt, author = "Holst, Michael and Lindblom, Lee and Owen, Robert and Pfeiffer, Harald P. and Scheel, Mark A. and Kidder, diff --git a/docs/Tutorials/CCE.md b/docs/Tutorials/CCE.md index 66da7d150cbf..89ed654aca65 100644 --- a/docs/Tutorials/CCE.md +++ b/docs/Tutorials/CCE.md @@ -181,13 +181,13 @@ complex modes in m-varies-fastest format. That is, Each dataset in the H5 file must also have an attribute named `Legend` which is an ASCII-encoded null-terminated variable-length string. -##### Spherical harmonic nodes {#spherical_nodes} +#### Spherical harmonic nodes {#spherical_nodes} When we refer to a "nodal" data format, we mean that the worldtube data are -stored as complex values at specially chosen collocation points (a.k.a. grid -points or nodes). This allows SpECTRE to perform integrals, derivatives, and -interpolation exactly on the input data. These grid points are Gauss-Legendre in -$cos(\theta)$ and equally spaced in $\phi$. +stored as values at specially chosen collocation points (a.k.a. grid points or +nodes). This allows SpECTRE to perform integrals, derivatives, and interpolation +exactly on the input data. These grid points are Gauss-Legendre in $cos(\theta)$ +and equally spaced in $\phi$. Below is a routine for computing the spherical harmonic $\theta$ and $\phi$ values. These can be used to compute the Cartesian @@ -223,6 +223,51 @@ named `Legend` which is an ASCII-encoded null-terminated variable-length string. \note Nodal data is likely the easiest to write out since no conversion to spherical harmonic coefficients is necessary. +#### ADM Cartesian metric and derivatives {#adm_cartesian_metric_and_derivatives} + +For worldtube data stored in an H5 file in the "ADM metric nodal" format, there +must be the following datasets with these exact names (including the `.dat` +suffix): + +- `gxx.dat`, `gxy.dat`, `gxz.dat`, `gyy.dat`, `gyz.dat`, `gzz.dat` +- `Dxgxx.dat`, `Dxgxy.dat`, `Dxgxz.dat`, `Dxgyy.dat`, `Dxgyz.dat`, `Dxgzz.dat` +- `Dygxx.dat`, `Dygxy.dat`, `Dygxz.dat`, `Dygyy.dat`, `Dygyz.dat`, `Dygzz.dat` +- `Dzgxx.dat`, `Dzgxy.dat`, `Dzgxz.dat`, `Dzgyy.dat`, `Dzgyz.dat`, `Dzgzz.dat` +- `Shiftx.dat`, `Shifty.dat`, `Shiftz.dat` +- `DxShiftx.dat`, `DxShifty.dat`, `DxShiftz.dat` +- `DyShiftx.dat`, `DyShifty.dat`, `DyShiftz.dat` +- `DzShiftx.dat`, `DzShifty.dat`, `DzShiftz.dat` +- `Lapse.dat`, `DxLapse.dat`, `DyLapse.dat`, `DzLapse.dat` +- `Kxx.dat`, `Kxy.dat`, `Kxz.dat`, `Kyy.dat`, `Kyz.dat`, `Kzz.dat` +- If BSSN: `AuxiliaryShiftx.dat`, `AuxiliaryShifty.dat`, `AuxiliaryShiftz.dat` +- If Z4c: `ConformalChristoffelx.dat`, `ConformalChristoffely.dat`, + `ConformalChristoffelz.dat` + +Here `g` represents the spacetime metric, but we only require the spatial +components (e.g. `gxx.dat`, `gxy.dat`, etc...) so in practice, those are the +tensor components of the spatial metric. The temporal components of the +spacetime metric are stored separately in the lapse and shift. Each of the +spatial metric, lapse, and shift must also have their cartesian derivatives. `K` +is the extrinsic curvature, `AuxiliaryShift` is the auxiliary shift vector used +in the Gamma-driver condition of the BBSN equations, and `ConformalChristoffel` +is the trace of the conformal christoffel symbols in the Z4c equations. We will +compute the time derivative of the spatial metric using Eq. (2.134) of +\cite BaumgarteShapiro, the time derivative of the lapse using the `1+log` +slicing condition from Eq. (4.87) of \cite BaumgarteShapiro, and the time +derivative of the shift using the Gamma-driver condition from Eq. (4.89) of +\cite BaumgarteShapiro if using BSSN or using the Gamma-driver condition from +Eq. (12) of \cite Hilditch:2012fp if using Z4c. + +\warning If your worldtube data is in the ADM metric nodal format but you have +not used `1+log` slicing and the Gamma-driver conditions specified above, your +time derivatives will be **wrong**. If you'd like us to support other commonly +used gauge conditions, please open an issue on our +[GitHub](https://github.com/sxs-collaboration/spectre) or email the core +developers at [core-devs@spectre-code.org](mailto:core-devs@spectre-code.org). + +The layout of each of these datasets must be +[spherical harmonic nodes](#spherical_nodes). + #### Cartesian metric and derivatives {#cartesian_metric_and_derivatives} For worldtube data stored in an H5 file in either the "metric nodal" or "metric @@ -251,7 +296,8 @@ each of these datasets must be in either In the "bondi nodal" format, you must have the same Bondi variables as the [required format](#required_h5_worldtube_data_format), but each variable layout -must be the [spherical harmonic nodal layout](#spherical_nodes). +must be the [spherical harmonic nodal layout](#spherical_nodes) with complex +values interleaved as `Re`, `Im`, `Re`, `Im`, ... If you already have data in the [required "bondi modal" format](#required_h5_worldtube_data_format), then @@ -297,6 +343,12 @@ Here are some notes about the different options in the YAML input file: *ascending* m. - `BufferDepth` is an advanced option that lets you load more data into RAM at once so there are fewer filesystem accesses. +- If using the ADM metric nodal input data format from a BSSN evolution, specify + the `InputDataFormat:` like so: +\snippet BssnPreprocessCceWorldtube.yaml bssn_input_data_format_example +- If using the ADM metric nodal input data format from a Z4c evolution, specify + the `InputDataFormat:` like so: +\snippet Z4cPreprocessCceWorldtube.yaml z4c_input_data_format_example ### What Worldtube data "should" look like {#worldtube_data_looks} @@ -310,7 +362,8 @@ The 2,2 modes are oscillatory and capture the orbits of the two objects. The real part of the 2,0 mode contains the gravitational memory of the system. Then for this system, all the other modes are subdominant. -If you are using the [cartesian metric](#cartesian_metric_and_derivatives) +If you are using the [cartesian metric](#cartesian_metric_and_derivatives) or +[adm cartesian metric](#adm_cartesian_metric_and_derivatives) worldtube format, here is a plot of the imaginary part of the 2,2 mode of the lapse and its radial and time derivative during inspiral. diff --git a/src/Executables/PreprocessCceWorldtube/PreprocessCceWorldtube.cpp b/src/Executables/PreprocessCceWorldtube/PreprocessCceWorldtube.cpp index cd7cc090cce5..5fbe4429d1f8 100644 --- a/src/Executables/PreprocessCceWorldtube/PreprocessCceWorldtube.cpp +++ b/src/Executables/PreprocessCceWorldtube/PreprocessCceWorldtube.cpp @@ -56,6 +56,8 @@ using modal_bondi_input_tags = Cce::Tags::worldtube_boundary_tags_for_writing< using nodal_bondi_input_tags = Cce::Tags::worldtube_boundary_tags_for_writing; +using AdmOptions = Cce::MetricWorldtubeH5BufferUpdater::AdmOptions; + // from a data-varies-fastest set of buffers provided by // `MetricWorldtubeH5BufferUpdater` extract the set of coefficients for a // particular time given by `buffer_time_offset` into the `time_span` size of @@ -326,12 +328,13 @@ void bondi_nodal_to_bondi_modal( } } -void metric_nodal_to_bondi_modal( - const std::string& input_file, const std::string& output_file, - const size_t input_buffer_depth, - const std::optional& extraction_radius) { +void metric_nodal_to_bondi_modal(const std::string& input_file, + const std::string& output_file, + const size_t input_buffer_depth, + const std::optional& extraction_radius, + const std::optional& adm_options) { Cce::MetricWorldtubeH5BufferUpdater buffer_updater{ - input_file, extraction_radius, false}; + input_file, extraction_radius, false, adm_options}; const size_t l_max = buffer_updater.get_l_max(); const size_t number_of_angular_points = @@ -377,7 +380,50 @@ void metric_nodal_to_bondi_modal( } } -enum class InputDataFormat { MetricNodal, MetricModal, BondiNodal, BondiModal }; +// If we use the AdmOptions class directly in an option tag, the input file +// would look like: +// +// InputDataFormat: +// AdvectiveLapse: True +// AdvectiveShift: True +// AuxiliaryShiftFactor: False +// +// However, this doesn't have the name `AdmMetricNodal`. This is consistent with +// how our options work, but is confusing (especially for users who are +// unfamiliar with spectre) since the actual input data format isn't shown. This +// class now changes the input file to look like: +// +// InputDataFormat: +// AdmMetricNodal: +// AdvectiveLapse: True +// AdvectiveShift: True +// AuxiliaryShiftFactor: False +// +// which is much clearer. +struct AdmMetricNodalOptions { + struct AdmMetricNodal { + using type = ::AdmOptions; + static constexpr Options::String help = ::AdmOptions::help; + }; + + using options = tmpl::list; + static constexpr Options::String help = ::AdmOptions::help; + + AdmMetricNodalOptions() = default; + // NOLINTNEXTLINE + AdmMetricNodalOptions(::AdmOptions adm_metric_nodal_in) + : adm_metric_nodal(adm_metric_nodal_in) {} + + ::AdmOptions adm_metric_nodal; +}; + +enum class InputDataFormat { + AdmMetricNodal, + MetricNodal, + MetricModal, + BondiNodal, + BondiModal +}; std::ostream& operator<<(std::ostream& os, const InputDataFormat input_data_format) { @@ -407,10 +453,11 @@ struct InputH5Files { }; struct InputDataFormat { - using type = ::InputDataFormat; + using type = std::variant<::InputDataFormat, AdmMetricNodalOptions>; static constexpr Options::String help = - "The type of data stored in the 'InputH5Files'. Can be 'MetricNodal', " - "'MetricModal', 'BondiNodal', or 'BondiModal'."; + "The type of data stored in the 'InputH5Files'. Can be 'AdmMetricNodal' " + "with additional options, 'MetricNodal', 'MetricModal', 'BondiNodal', or " + "'BondiModal'."; }; struct OutputH5File { @@ -496,11 +543,23 @@ struct InputH5Files : db::SimpleTag { }; struct InputDataFormat : db::SimpleTag { - using type = ::InputDataFormat; + private: + using option_type = std::variant<::InputDataFormat, AdmMetricNodalOptions>; + + public: + using type = std::variant<::InputDataFormat, AdmOptions>; using option_tags = tmpl::list; static constexpr bool pass_metavariables = false; - static type create_from_options(type input_data_format) { - return input_data_format; + static type create_from_options(option_type input_data_format) { + type result{}; + if (std::holds_alternative<::InputDataFormat>(input_data_format)) { + result = std::get<::InputDataFormat>(input_data_format); + } else { + result = + std::get(input_data_format).adm_metric_nodal; + } + + return result; } }; @@ -582,8 +641,8 @@ struct Options::create_from_yaml { return InputDataFormat::BondiModal; } PARSE_ERROR(options.context(), - "InputDataFormat must be 'MetricNodal', 'MetricModal', " - "'BondiNodal', or 'BondiModal'"); + "InputDataFormat must be 'AdmMetricNodal', 'MetricNodal', " + "'MetricModal', 'BondiNodal', or 'BondiModal'"); } }; @@ -638,8 +697,12 @@ int main(int argc, char** argv) { const TagsTuple inputs = Parallel::create_from_options(options, tags{}); - const InputDataFormat input_data_format = + const auto& input_data_format = tuples::get(inputs); + const InputDataFormat input_data_format_enum = + std::holds_alternative(input_data_format) + ? std::get(input_data_format) + : InputDataFormat::AdmMetricNodal; const std::vector& input_files = tuples::get(inputs); const std::string& output_h5_file = @@ -651,7 +714,7 @@ int main(int argc, char** argv) { // If the input format is BondiModal, then we don't actually have to do // any transformations, only combining H5 files. So the temporary file // name is just the output file - if (input_data_format == InputDataFormat::BondiModal) { + if (input_data_format_enum == InputDataFormat::BondiModal) { temporary_combined_h5_file = output_h5_file; } else { // Otherwise we have to do a transformation so a temporary H5 file is @@ -668,7 +731,7 @@ int main(int argc, char** argv) { // Now combine the h5 files into a single file h5::combine_h5_dat(input_files, temporary_combined_h5_file.value(), Verbosity::Quiet); - } else if (input_data_format == InputDataFormat::BondiModal) { + } else if (input_data_format_enum == InputDataFormat::BondiModal) { // Error here if the input data format is BondiModal since there's nothing // to do ERROR_NO_TRACE( @@ -678,7 +741,7 @@ int main(int argc, char** argv) { } if (tuples::get(inputs)) { - if (input_data_format != InputDataFormat::MetricModal) { + if (input_data_format_enum != InputDataFormat::MetricModal) { ERROR_NO_TRACE( "The option FixSpecNormalization can only be 'true' when the input " "data format is MetricModal. Otherwise, it must be 'false'"); @@ -702,12 +765,13 @@ int main(int argc, char** argv) { } }; - switch (input_data_format) { - case InputDataFormat::BondiModal: + switch (input_data_format_enum) { + case InputDataFormat::BondiModal: { // Nothing to do here because this is the desired output format and the // H5 files were combined above return 0; - case InputDataFormat::BondiNodal: + } + case InputDataFormat::BondiNodal: { bondi_nodal_to_bondi_modal( input_worldtube_filename(), output_h5_file, tuples::get(inputs), @@ -715,7 +779,8 @@ int main(int argc, char** argv) { clean_temporary_file(); return 0; - case InputDataFormat::MetricModal: + } + case InputDataFormat::MetricModal: { perform_cce_worldtube_reduction( input_worldtube_filename(), output_h5_file, tuples::get(inputs), @@ -726,16 +791,25 @@ int main(int argc, char** argv) { clean_temporary_file(); return 0; - case InputDataFormat::MetricNodal: + } + case InputDataFormat::AdmMetricNodal: { + [[fallthrough]]; + } + case InputDataFormat::MetricNodal: { + const std::optional adm_options = + std::holds_alternative(input_data_format) + ? std::optional{std::get(input_data_format)} + : std::nullopt; metric_nodal_to_bondi_modal( input_worldtube_filename(), output_h5_file, tuples::get(inputs), - tuples::get(inputs)); + tuples::get(inputs), adm_options); clean_temporary_file(); return 0; + } default: - ERROR("Unknown input data format " << input_data_format); + ERROR("Unknown input data format " << input_data_format_enum); } } catch (const std::exception& exception) { Parallel::printf("%s\n", exception.what()); diff --git a/tests/InputFiles/PreprocessCceWorldtube/BssnPreprocessCceWorldtube.yaml b/tests/InputFiles/PreprocessCceWorldtube/BssnPreprocessCceWorldtube.yaml new file mode 100644 index 000000000000..eb17798bce0d --- /dev/null +++ b/tests/InputFiles/PreprocessCceWorldtube/BssnPreprocessCceWorldtube.yaml @@ -0,0 +1,17 @@ +# Distributed under the MIT License. +# See LICENSE.txt for details. + +InputH5File: InputFilename.h5 +OutputH5File: ReducedWorldtubeR0292.h5 +# [bssn_input_data_format_example] +InputDataFormat: + AdmMetricNodal: + AdvectiveLapse: True + AdvectiveShift: True + BssnAuxiliaryShiftFactor: 0.75 # Eq. 4.89 B&S +# [bssn_input_data_format_example] +ExtractionRadius: 292 +FixSpecNormalization: False +DescendingM: False +BufferDepth: Auto +LMaxFactor: 3 diff --git a/tests/InputFiles/PreprocessCceWorldtube/Z4cPreprocessCceWorldtube.yaml b/tests/InputFiles/PreprocessCceWorldtube/Z4cPreprocessCceWorldtube.yaml new file mode 100644 index 000000000000..ce29c6eff04c --- /dev/null +++ b/tests/InputFiles/PreprocessCceWorldtube/Z4cPreprocessCceWorldtube.yaml @@ -0,0 +1,17 @@ +# Distributed under the MIT License. +# See LICENSE.txt for details. + +InputH5File: InputFilename.h5 +OutputH5File: ReducedWorldtubeR0292.h5 +# [z4c_input_data_format_example] +InputDataFormat: + AdmMetricNodal: + AdvectiveLapse: True + AdvectiveShift: True + Z4cShiftFactor: 2.0 # Eq. 12 Hilditch 2013 +# [z4c_input_data_format_example] +ExtractionRadius: 292 +FixSpecNormalization: False +DescendingM: False +BufferDepth: Auto +LMaxFactor: 3 diff --git a/tests/Unit/Executables/Test_PreprocessCceWorldtube.cpp b/tests/Unit/Executables/Test_PreprocessCceWorldtube.cpp index 4a72d2821a2c..304465c17116 100644 --- a/tests/Unit/Executables/Test_PreprocessCceWorldtube.cpp +++ b/tests/Unit/Executables/Test_PreprocessCceWorldtube.cpp @@ -268,7 +268,24 @@ void write_input_file(const std::string& input_data_format, } input_file += "\nOutputH5File: " + output_filename + "\n"; - input_file += "InputDataFormat: " + input_data_format + "\n"; + input_file += "InputDataFormat:"; + if (input_data_format == "BssnMetricNodal") { + input_file += + "\n" + " AdmMetricNodal:\n" + " AdvectiveLapse: True\n" + " AdvectiveShift: True\n" + " BssnAuxiliaryShiftFactor: 0.75\n"; + } else if (input_data_format == "Z4cMetricNodal") { + input_file += + "\n" + " AdmMetricNodal:\n" + " AdvectiveLapse: True\n" + " AdvectiveShift: True\n" + " Z4cShiftFactor: 2.0\n"; + } else { + input_file += " " + input_data_format + "\n"; + } input_file += "ExtractionRadius: " + (worldtube_radius.has_value() ? std::to_string(worldtube_radius.value()) @@ -307,6 +324,10 @@ int main() { "Test_InputMetricModal_R0123.h5"}; const std::string metric_modal_spec_input_worldtube_filename{ "Test_InputMetricModalSpec_R0123.h5"}; + const std::string bssn_metric_nodal_input_worldtube_filename{ + "Test_InputBssnMetricNodal.h5"}; + const std::string z4c_metric_nodal_input_worldtube_filename{ + "Test_InputZ4cMetricNodal.h5"}; const std::string metric_nodal_1_input_worldtube_filename{ "Test_InputMetricNodal_1.h5"}; const std::string metric_nodal_2_input_worldtube_filename{ @@ -323,6 +344,10 @@ int main() { "Test_OutputMetricModal_R0123.h5"}; const std::string metric_modal_spec_output_worldtube_filename{ "Test_OutputMetricModalSpec_R0123.h5"}; + const std::string bssn_metric_nodal_output_worldtube_filename{ + "Test_OutputBssnMetricNodal.h5"}; + const std::string z4c_metric_nodal_output_worldtube_filename{ + "Test_OutputZ4cMetricNodal.h5"}; const std::string metric_nodal_output_worldtube_filename{ "Test_OutputMetricNodal.h5"}; const std::string bondi_modal_output_worldtube_filename{ @@ -337,6 +362,12 @@ int main() { Cce::TestHelpers::write_test_file( solution, metric_modal_spec_input_worldtube_filename, target_time, worldtube_radius, frequency, amplitude, l_max, true); + Cce::TestHelpers::write_test_file( + solution, bssn_metric_nodal_input_worldtube_filename, target_time, + worldtube_radius, frequency, amplitude, l_max, false, true); + Cce::TestHelpers::write_test_file( + solution, z4c_metric_nodal_input_worldtube_filename, target_time, + worldtube_radius, frequency, amplitude, l_max, false, true); Cce::TestHelpers::write_test_file( solution, metric_nodal_1_input_worldtube_filename, target_time, worldtube_radius, frequency, amplitude, l_max, false); @@ -363,6 +394,14 @@ int main() { {metric_modal_spec_input_worldtube_filename}, metric_modal_spec_output_worldtube_filename, std::nullopt, true); + write_input_file("BssnMetricNodal", "BssnMetricNodal.yaml", + {bssn_metric_nodal_input_worldtube_filename}, + bssn_metric_nodal_output_worldtube_filename, + {worldtube_radius}, false); + write_input_file("Z4cMetricNodal", "Z4cMetricNodal.yaml", + {z4c_metric_nodal_input_worldtube_filename}, + z4c_metric_nodal_output_worldtube_filename, + {worldtube_radius}, false); write_input_file("MetricNodal", "MetricNodal.yaml", {metric_nodal_1_input_worldtube_filename, metric_nodal_2_input_worldtube_filename}, @@ -407,6 +446,8 @@ int main() { // Call PreprocessCceWorldtube in a shell call_preprocess_cce_worldtube("MetricModal"); call_preprocess_cce_worldtube("MetricModalSpec"); + call_preprocess_cce_worldtube("BssnMetricNodal"); + call_preprocess_cce_worldtube("Z4cMetricNodal"); call_preprocess_cce_worldtube("MetricNodal"); call_preprocess_cce_worldtube("BondiModal"); call_preprocess_cce_worldtube("BondiNodal"); @@ -419,7 +460,10 @@ int main() { solution, amplitude, frequency); // Check that the expected bondi modal data is what was written in the output - // files for the different InputDataFormats + // files for the different InputDataFormats. We don't check BssnMetricNodal or + // Z4cMetricNodal because our expected data uses a different gauge than is + // assumed (1+log and gamma driver), and so some time derivatives will be + // incorrect. check_expected_data(metric_modal_output_worldtube_filename, l_max, expected_data, second_expected_data, target_time, second_target_time, false);