Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inputs refactor and extra checking #247

Merged
merged 58 commits into from
Sep 18, 2024
Merged

Conversation

johnomotani
Copy link
Collaborator

@johnomotani johnomotani commented Sep 9, 2024

Organize the rest of the options that were still at the top level of the input file into sections. It would be easy to handle top-level options if we wanted them (I actually already wrote set_defaults_and_check_top_level!() for the makie_post_processing option reader), but I didn't see at the moment any options that it didn't make sense to put in some section.

All options are now declared using set_defaults_and_check_section!() so we check that each option that is given in the input exists. This PR also adds check_sections!() which checks that all the sections that are given in the input exist. This makes it impossible for a typo or out of date input file to mean an option that was meant to be set is not set - the 'worst' that could happen would be for a mistake to change the name of one option into a different option, but that should be pretty unlikely. At least nothing will be silently ignored now.

While modifying the 'options', takes the opportunity to remove the remaining mutable struct definitions in input_structs.jl to remove duplication (a couple of the mutable structs were already not being used). The final options objects are still immutable, as they are (mostly) NamedTuples created with Dict_to_NamedTuple().

The new sections are:

  • [r], [z], [vperp], [vpa], [vzeta], [vr], [vz] for the coordinates inputs
  • [em_fields] - so far just for the force_Er_zero_at_wall option
  • [evolve_moments] for the moment-kinetic flags, which are now density, parallel_flow, parallel_pressure, and moments_conservation
  • [reactions] for the ion-neutral interaction parameters
  • [electron_fluid_collisions] for the nu_ei parameter used by the braginskii_fluid electron model
  • run_name and base_directory move into the [output] section

update-input-file.jl.zip is a (zip'ed) script that will convert old input files to the new format. You might want to run it to get a new input file, then copy over the new sections by hand to your original file, because the script will remove all comments and might reorder options and sections. It's currently written to rename the original file with a .unmodified suffix, but you could easily edit to add a suffix to the new filename instead if you prefer.

There are a couple of minor changes to defaults: the defaults for vz are no longer taken from the settings for vpa; the default for neutral_numerical_dissipation:vz_dissipation_coefficient is no longer taken from ion_numerical_dissipation:vpa_dissipation_coefficient. I don't think we often (ever?) relied on these defaults anyway, so hopefully this is not a disruptive change.

Closes #241.

Edit 11/9/2024: replaced the input file updater script with a fixed version. Thanks @LucasMontoya4 for noticing the bug!

Also tidy up the coordinate initialisation.
recursive_merge() is more flexible, as it can handle arbitrarily deep
nesting of Dicts.

Also move merge_dict_with_kwargs() to utils module, as it is only needed
in the tests, not within the moment_kinetics module itself.
The Github CI check for the examples does not use distributed MPI, so
nelement_local should not be set in the examples.
This tests a feature that was never added to moment_kinetics.
Create a section to contain the `force_Er_zero_at_wall` option (and
others in future).

Remove the unused `drive_input` structs.
This is probably more confusing than helpful. By removing it, all
dissipation coefficients have to be set explicitly.
Unexpected section names are probably typos, so we error so that the
user can fix them rather than having any options in them be silently
ignored.
This setting is now in the [timestepping] section.
This variable is no longer only used to set up parameter scans, so give
it a more logical name.
...so that it at least works for the 1D1V case, and errors otherwise.
A current test case (long tests in restart_interpolation_tests.jl,
before adding a workaround for the run_name length in that test) crashes
when the filename is 245 characters, and not when the filename is 243
characters.
@johnomotani johnomotani added the enhancement New feature or request label Sep 9, 2024
@johnomotani
Copy link
Collaborator Author

PS this PR also adds a CI job that checks the scripts in test_scripts/ run without errors.

@johnomotani
Copy link
Collaborator Author

This PR is pretty big because of all the changes to input and test files, but for the source code files in moment_kinetics/src/ it reduces the number of lines of code by 424 (+718 -1142), so I can claim it simplifies things a bit!

Copy link
Collaborator

@mrhardman mrhardman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making this huge effort! I have a few questions about syntax that I'd like to discuss before going forward. My two big questions are "why remove input structs?" and "why pass input data of multiple coordinates to define_coordinate?".

moment_kinetics/src/coordinates.jl Show resolved Hide resolved
moment_kinetics/src/coordinates.jl Show resolved Hide resolved
moment_kinetics/src/gauss_legendre.jl Outdated Show resolved Hide resolved
moment_kinetics/src/input_structs.jl Outdated Show resolved Hide resolved
moment_kinetics/src/load_data.jl Show resolved Hide resolved
neutral_input_dict = Dict(Symbol(k)=>v for (k,v) in neutral_input)
neutral_params = neutral_num_diss_params(; neutral_input_dict...)

return numerical_dissipation_parameters(ion_params, electron_params, neutral_params)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why replace this struct with a NamedTuple? Since this data is clearly getting passed around, it would seem useful to make this data something that cannot be modified afterwards.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedTuple is immutable.

I want to use set_defaults_and_check_section!() to read the inputs, because that's the function that registers the section as being 'expected' so we can check that only expected sections exist. Also, set_defaults_and_check_section!() has nicer error messages.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have nothing against set_defaults_and_check_section!(). My concern here was just about the object returned by the function. For geometry and composition and etc, we use explicitly defined structs. I find these easier to understand because the typing documentation is explicitly given somewhere. Happy to hear that NamedTuple is immutable, would be even happier if there was an explicit type definition of everything that goes into the NamedTuple (i.e., it was a struct : ) ). This is probably just a preference based on the other existing namelist options.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there's not quite an ideal solution. I'd like to only 'declare' the options in one place - either as the fields of a struct or as the arguments to set_defaults_and_check_section!(). The trouble is that: if you declare the fields and the defaults in a struct definition, then the defaults have to be constants; if you use get_defaults_and_check_section!() to get an OptionsDict with the right variables, you'd have to declare them twice to have a struct that contains the same variables.

Using NamedTuple is a compromise where you do the second option, but have to live with having an implicitly-defined NamedTuple rather than an explicitly-defined struct. Sometimes having the names declared twice so you can have the struct will be the best thing to do.

See the new definition of initial conditions in 3ad41d6 for an example of the 'first option'.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I acknowledge the two options, and suggest a third -- my preference is for an option always involving an explicitly defined struct, at the expense of declaring the variables twice to get the right defaults, where necessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an option, and we do use it in places. I find the NamedTuple clear enough (as the usage is pretty standard, the fields are defined in a call to set_defaults_and_check_section!() in a 'setup' function for some module) that it's worth using (sometimes) for the benefit of being easier to update.

I don't think we're going to agree on this. @mabarnes @LucasMontoya4 do you have an opinion? I think to sum up, the question is:
In the case where we want the default for one or more options to not just be a constant value (i.e. depend on another option, or some other parameters) there are two options

  1. Define a struct for settings (using @kwdef); call set_defaults_and_check_section!() with the keyword arguments being the fields of the struct, creating an OptionsDict; do some processing to update the default values as necessary, modifying the OptionsDict; construct an instance of the struct using the OptionsDict.
  2. Call set_defaults_and_check_section!() with the keyword arguments being the fields of the struct, creating an OptionsDict; do some processing to update the default values as necessary, modifying the OptionsDict; use Dict_to_NamedTuple to create a NamedTuple (which is an immutable, implicitly defined struct) from the OptionsDict.
    1 has an explicitly declared struct, but requires all the fields to be 'defined' twice (once in the struct definition and once in the call to set_defaults_and_check_section!(). 2 defines the fields only once, but creates a NamedTuple with those fields.

Should we require 1 or 2, or leave it up to whoever is writing the module to decide which seems more convenient?

PS in this particular instance, we could avoid this issue by having constant defaults if we didn't bother to replace -Inf with nothing for force_minimum_pdf_value. I think using nothing is marginally more efficient because the if can be evaluated at compile-time, but the cost will be totally negligible anyway because it's one float comparison that's outside any loop over arrays. Maybe that would be the best option in this instance!

Copy link
Collaborator Author

@johnomotani johnomotani Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, maybe this is not such a big issue after all. I got into the habit of using NamedTuples when set_default_and_check_section!() couldn't handle structs at all, but this discussion with @mrhardman prompted me to add the version that can directly create an @kwdef struct as long as the defaults are constant. Now (with the commits I just pushed) using that version where possible, there are only a few places where we create settings in a NamedTuple and:

  • evolve_moments settings don't propagate far, only until they get put into the moments struct.
  • output from get_coordinate_input() only gets used inside or fed into define_coordinate().
  • input for manufactured solutions is only used within that module, and it's useful to make adding new inputs as simple as possible there.
  • the nonlinear_solvers functionality is still WIP, so it's useful to be able to change it simply (at least for now).
  • The reference parameters aren't used in many places, and we might want to add more derived parameters.

moment_kinetics/test/gyroaverage_tests.jl Show resolved Hide resolved
moment_kinetics/src/coordinates.jl Show resolved Hide resolved
moment_kinetics/test/calculus_tests.jl Outdated Show resolved Hide resolved
johnomotani and others added 9 commits September 11, 2024 18:04
Thanks to Patrick Farrell for guidance on periodic boundary conditions
in finite element codes!
Helps to prevent special properties of test function (e.g. symmetry, or
being zero on the periodic boundary - not sure what is actually
relevant!) from masking errors in the implementation.
...without even rounding errors, as these might drift and accumulate
into a non-negligible error after many timesteps.
Ensures an error will be thrown if `L_matrix` is used when it has not
been implemented, and removes the need for a noisy warning.
… between periodic BC modifications required for matrices on LHS or RHS in LHS.x = RHS. The ODE solver in the script sets RHS[end] = 0.0 so second version of mass matrix is avoided. However, L_matrix_with_bc appears to be singular in some examples, suggesting a continuing problem.
…eriodic case, this is one to make the system periodic, and one to fix the constant offset. This should remove the problems with a singular L matrix.
As the input can be assumed to be periodic when using periodic bc, it is
not necessary to zero out the last row of the 'rhs' matrix - it is ok to
use a similar form (1 0 0 ... 0 0 -1) as the last row of the 'lhs'
matrix, because the two non-zero entries will cancel exactly due to the
periodicity. This is convenient because it means we do not need to
distinguish between 'lhs' and 'rhs' matrices - we can use the same
matrix for both functions.
@johnomotani johnomotani force-pushed the coordinates-input-refactor branch from 3663d04 to 0405d08 Compare September 16, 2024 11:45
The explicitly defined structs for numerical dissipation parameters
were removed to use `get_defaults_and_check_section!()`, but now that
`get_defaults_and_check_section!()` can handle an `@kwdef struct`, it
makes sense to put the structs back.
@johnomotani johnomotani force-pushed the coordinates-input-refactor branch from 0405d08 to ba41f32 Compare September 16, 2024 12:02
Having constant defaults rather than ones that depend on other input
parameters makes the setup easier to define, and the previous defaults
were not physically meaningful anyway.
The name for the 'stop file' used to be changeable in the timestepping
options, but if it was set would have to be set with either an absolute
path or a path relative to where `julia` is running, not relative to the
output directory, so this option wasn't very helpful anyway. Now remove
the option, and just hard code the name to "stop".
Also define a `setup_io_input()` function to simplify `mk_input()`.
Comment said "end user inputs. do not modify following code!" but some
setup functions have to come later, e.g. because they use coordinates,
and users have not had to modify the `mk_input()` function to change
parameters for a long time.
@johnomotani johnomotani force-pushed the coordinates-input-refactor branch from ba41f32 to 0458ab0 Compare September 16, 2024 12:19
NetCDF seems to break the parallel tests. Don't understand why.
When an `@kwdef struct` is passed to `set_defaults_and_check_section!()`
so that the defaults are applied by the struct, also load the final
values back into the `options` Dict so that they can be saved to the
output file.

Note that there is no null value in TOML, or in HDF5, so default values
of `nothing` are not allowed, as these could not be saved. Throw a
helpful error if `nothing` is found in options, also add a note in the
docs about this.

Some defaults in [numerical_dissipation], [timestepping],
[electron_timestepping] and [output] sections had to be changed from
`nothing`.
To ensure that we are notified of any warnings introduced, e.g. by
mistakes in docstrings, so we know to fix them before we merge the PR
and deploy the new docs.
@johnomotani johnomotani merged commit b0cce85 into master Sep 18, 2024
21 checks passed
@johnomotani johnomotani deleted the coordinates-input-refactor branch September 18, 2024 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor namelist inputs for coordinates and other inputs
2 participants