Concretize IoData defaults and write out resolved config#719
Conversation
dffb2d0 to
2170b75
Compare
6dbf12c to
4baca74
Compare
dfda2c5 to
592644b
Compare
simlapointe
left a comment
There was a problem hiding this comment.
Overall LGTM. Tested it on many cases and it seems to work as expected.
A few small things:
- This may be intended, but I find the AMR behavior slightly weird:
config.jsongets written in theiteration1folder (but not subsequent iterations) and a symlink to it is created in the top-level output folder. Maybe having the actual file in the top-level output folder and not in any of theiteration*folders makes more sense? - Maybe unlikely, but if the input config is
config.jsonand the user sets"Output": ".", then the resolved config overwrites the input config. Should we guard against this? - It would be nice if the resolved config would get printed when running
--dry-run, but maybe that's asking for too much? - See comment below about the new
max_size/num_vecdefault.
| // Resolve subspace sentinel: fed directly into ModeEigenSolver → SLEPc/ARPACK. | ||
| if (max_size <= 0) | ||
| { | ||
| max_size = std::max(2 * mode_idx, mode_idx + 15); |
There was a problem hiding this comment.
Previously we were using max(20, 2 * mode_idx + 1), which is the SLEPc default, correct? I know the differences should be minor, but is there a particular reason to change to max(2 * mode_idx, mode_idx + 15)?
There was a problem hiding this comment.
The old line was commented "Default from SLEPc," but that's a mislabel — max(20, 2*nev+1) isn't SLEPc's default; max(2*nev, nev+15) is. The two backends were silently resolving an unset MaxSize differently, so this unifies them on SLEPc's actual formula and makes the resolved config backend-independent. The cost is a slightly smaller default for small nev (e.g. nev=4: 20→19, nev=1: 20→16), though these stay well above nev and all in-tree examples set MaxSize explicitly, so nothing in the repo changes.
There was a problem hiding this comment.
Thank you, really useful feature! I left some comments, but nothing very big. I tested with a bunch of our examples in the repo.
I am a little concerned with the amount of information that is represented in various different ways (from JSON to C++ to concretizer...), and I think this further motivates generating this code from a single source (not in this PR).
| return config; | ||
| } | ||
|
|
||
| void IoData::WriteResolvedConfig(const json &raw_config) const |
There was a problem hiding this comment.
This will break the user's config is if <problem.output> = "." and the user's config is named config.json.
We might want to check if a file already exists before writing.
| // DEFAULT is an alias for GEN_ALPHA; placing it last ensures to_json emits | ||
| // "GeneralizedAlpha" rather than "Default" while still accepting "Default" as input. |
There was a problem hiding this comment.
I think this is a really great feature.
I tested it with some of our examples and found that there are still some "Defaults" left:
"ColumnOrdering": "Default",
"PCSide": "Default",
There was a problem hiding this comment.
These are intentionally left as "Default" — Palace delegates the choice to the downstream library (GMRES/FGMRES selects the PC side; the sparse direct solver selects the ordering), so there is no concrete value for Palace to emit. Documented as such in solver.md and postprocessing.md (3466612).
| for i = 1:max_its | ||
| push!(expected_metafiles, "iteration$(i)/palace.json") | ||
| end |
There was a problem hiding this comment.
this should probably also take into account the resolved config file (or we should avoid copying the resolved config file in each iteration)
There was a problem hiding this comment.
Fixed in 3466612 — resolved config is now a single top-level <input>_resolved.json, no per-iteration copies, and the harness checks for it there.
| } | ||
| nev = num_eig; | ||
| ncv = (num_vec > 0) ? num_vec : std::max(20, 2 * nev + 1); // Default from SLEPc | ||
| ncv = num_vec; |
There was a problem hiding this comment.
Just noting that this changes the default when num_vec <= 4
There was a problem hiding this comment.
Acknowledged — see the explanation on the configfile.cpp MaxSize thread. Both backends now resolve an unset subspace to SLEPc's actual default max(2*nev, nev+15); the change is deliberate and documented.
| - Palace now writes a fully-resolved copy of the run configuration to | ||
| `<output_dir>/config.json` at startup, with every implicit default and `"Default"` | ||
| sentinel filled in. The sidecar passes schema validation and can be used to re-run | ||
| the same simulation deterministically [PR 719](https://github.com/awslabs/palace/pull/719). |
There was a problem hiding this comment.
We should document this in the docs as well.
There was a problem hiding this comment.
Documented in 3466612 (new "Resolved configuration record" section in docs/src/guide/postprocessing.md, plus expanded PCSide/ColumnOrdering notes in solver.md).
| } | ||
| if (max_size <= 0) | ||
| { | ||
| max_size = std::max(2 * n, n + 15); |
There was a problem hiding this comment.
Maybe we can add a function for this default, since we set it in multiple places?
There was a problem hiding this comment.
Fixed in 3466612 — extracted DefaultEigenSubspaceSize(num_modes) and routed all three call sites through it.
| CHECK(iodata.solver.linear.ams_singular_op == 1); | ||
| CHECK(iodata.solver.linear.ams_max_it == 1); | ||
| CHECK(iodata.solver.linear.mg_cycle_it == 1); | ||
| CHECK(iodata.solver.linear.pc_mat_sym == MatrixSymmetry::SPD); |
There was a problem hiding this comment.
MatrixSymmetry is an object implicitly defined and not a config. I don't think it should be in the solver.linear struct (which would otherwise represent the JSON faithfully).
There was a problem hiding this comment.
Fixed in 032a091 — removed from LinearSolverData and derived at construction via GetPreconditionerMatrixSymmetry(iodata) in ksp.cpp.
| namespace palace | ||
| { | ||
|
|
There was a problem hiding this comment.
I think we could remove this entire file by inlining the operator in PALACE_JSON_SERIALIZE_ENUM
There was a problem hiding this comment.
I kept the file but addressed the underlying duplication. There is now one ToString/FromString mapping table per enum, and both the JSON to_json/from_json shims and the resolved-config writer delegate to it, so there is a single source of truth either way. I left a small declaration header rather than inlining into PALACE_JSON_SERIALIZE_ENUM for two reasons: the resolved-config writer needs ToString without pulling in the full nlohmann serialization machinery, and keeping the tables in a .cpp avoids re-expanding them at every macro site. (d22986c)
| } | ||
| } | ||
|
|
||
| void ConcretizeProblem(const config::ProblemData &problem, json &j_problem) |
There was a problem hiding this comment.
In the future, this entire file could (should?) probably be generated from the schema in the spirit of #723
There was a problem hiding this comment.
Agreed, and #723 is the right vehicle. This hand-written concretizer plus the schema coverage gate is the stopgap until then — I'd keep codegen out of this PR's scope. The coverage test fails loudly if a schema property ever drifts out of sync in the meantime.
82b2575 to
d22986c
Compare
|
Current spack CI failures will be fixed once rebased on top of #742 once that is merged. |
d22986c to
2ea63c6
Compare
…figuration Make DEFAULT a distinct enum value and resolve it to the concrete backend during CheckConfiguration so drivers never observe the sentinel.
…Configuration Fold the PETSC_DEFAULT / std::max(20, 2*n+1) fallbacks from the SLEPc and ARPACK wrappers into CheckConfiguration, and pick Palace defaults (max_it = 1'000'000, max_size = max(2*n, n+15)) so IoData is concrete post-construction.
Write a fully-resolved copy of the configuration to <stem>-resolved.json in the output directory — every implicit default filled in so the file alone is enough to re-run the simulation. IoData::ConcretizeDefaults takes the parsed config by value, fills any missing keys (and replaces "Default" sentinel strings) from the resolved IoData, and returns it for main.cpp to stream to disk. Build-availability guards for SLEPc/ARPACK/SuperLU/STRUMPACK/MUMPS are centralised in CheckConfiguration. IoData gains a static ParseAndValidate so main.cpp can feed one JSON to both construction and concretization.
Palace drops <stem>-resolved.json next to palace.json in the output directory. Normalise the tempname-derived stem to a fixed "-resolved.json" key and add it to expected_metafiles so the harness asserts the sidecar is produced.
Every defaulted field in ModelData, RefinementData, FarfieldBoundaryData, ConductivityData, ImpedanceData, LumpedPortData, WavePortData, PeriodicBoundaryData, and the Boundaries.Postprocessing sub-structs is now written to the resolved config.
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
…ter" This reverts commit 9954bcb.
…ation Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
…ration Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
Signed-off-by: Hugh Carson <hughcars@amazon.com>
- Name resolved config <input>_resolved.json to avoid overwriting input - Keep resolved config at top level during AMR (skip iteration sweep) - Update regression harness to expect top-level _resolved.json - Print resolved configuration on --dry-run - Factor out DefaultEigenSubspaceSize helper for eigen subspace default - Document PCSide/ColumnOrdering as library-delegated defaults - Document resolved-config sidecar in postprocessing guide and CHANGELOG
…ig struct pc_mat_sym was a derived runtime property stored on LinearSolverData, which should mirror the JSON config faithfully. Move the derivation into a free GetPreconditionerMatrixSymmetry(IoData) helper used at Ksp construction, thread the resulting MatrixSymmetry through the linear-only BaseKspSolver ctor, and remove the field. Also drops the now-dead MumpsSolver(IoData,...) ctor.
…root The concretizer's EnumString/operator<< was a second enum->string mechanism redundant with nlohmann to_json. Move the one mapping table per enum into enum_string.cpp behind ToString/FromString; the JSON to_json/from_json shims and the resolved-config writer now delegate to it. Removes operator<<, EnumString, and the triple-duplicated mapping arrays in the macro.
8cf56cc to
c9d909b
Compare
Palace now drops
config.jsonalongsidepalace.jsonin the output directory. Every implicit default and"Default"sentinel is filled in so the file alone is enough to reproduce the run.IoData::ConcretizeDefaultstakes the parsed config by value, fills keys the user left blank (and replaces"Default"sentinel strings) from the resolvedIoData, and returns the result formain.cppto stream to disk. User-provided entries pass through untouched.A schema-driven coverage gate (
SchemaCoverageGapsintest/unit/test-config.cpp) walks every section of the JSON schema and asserts that every optional property with a C++ default is emitted, modulo a small justified skip-list.Supporting cleanup:
EigenSolverBackend::DEFAULTandTimeSteppingScheme::DEFAULTare now type-level aliases for their build-determined / canonical concrete value, so a default-constructed field is already at its concrete value and no runtime sentinel resolution is needed.Solver.Eigenmode.MaxItsandMaxSizedefaults are resolved inside the struct's JSON constructor:MaxIts = 1'000'000(iteration cap only, no buffer),MaxSize = max(2*n, n+15)(SLEPc's Krylov-Schur formula; previously ARPACK silently usedmax(20, 2*n+1)). Same treatment forSolver.BoundaryMode.MaxSizeand per-portBoundaries.WavePort[].MaxSize— these previously fed-1into the SLEPc/ARPACK wrappers and segfaulted any caller (including the boundary-mode unit tests) that didn't trip a defensive fallback that no longer exists. All in-tree examples set these explicitly.config.jsonsidecar is produced for every example.