Trixi.jl
Trixi.jl copied to clipboard
Sutherlands Law for temperature dependent viscosity
Closes #1195
Review checklist
This checklist is meant to assist creators of PRs (to let them know what reviewers will typically look for) and reviewers (to guide them in a structured review process). Items do not need to be checked explicitly for a PR to be eligible for merging.
Purpose and scope
- [ ] The PR has a single goal that is clear from the PR title and/or description.
- [ ] All code changes represent a single set of modifications that logically belong together.
- [ ] No more than 500 lines of code are changed or there is no obvious way to split the PR into multiple PRs.
Code quality
- [ ] The code can be understood easily.
- [ ] Newly introduced names for variables etc. are self-descriptive and consistent with existing naming conventions.
- [ ] There are no redundancies that can be removed by simple modularization/refactoring.
- [ ] There are no leftover debug statements or commented code sections.
- [ ] The code adheres to our conventions and style guide, and to the Julia guidelines.
Documentation
- [ ] New functions and types are documented with a docstring or top-level comment.
- [ ] Relevant publications are referenced in docstrings (see example for formatting).
- [ ] Inline comments are used to document longer or unusual code sections.
- [ ] Comments describe intent ("why?") and not just functionality ("what?").
- [ ] If the PR introduces a significant change or new feature, it is documented in
NEWS.md
.
Testing
- [ ] The PR passes all tests.
- [ ] New or modified lines of code are covered by tests.
- [ ] New or modified tests run in less then 10 seconds.
Performance
- [ ] There are no type instabilities or memory allocations in performance-critical parts.
- [ ] If the PR intent is to improve performance, before/after time measurements are posted in the PR.
Verification
- [ ] The correctness of the code was verified using appropriate tests.
- [ ] If new equations/methods are added, a convergence test has been run and the results are posted in the PR.
Created with :heart: by the Trixi.jl community.
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 93.07%. Comparing base (
909abb4
) to head (042b18e
).
Additional details and impacted files
@@ Coverage Diff @@
## main #1808 +/- ##
==========================================
- Coverage 96.30% 93.07% -3.23%
==========================================
Files 440 441 +1
Lines 35793 35798 +5
==========================================
- Hits 34470 33317 -1153
- Misses 1323 2481 +1158
Flag | Coverage Δ | |
---|---|---|
unittests | 93.07% <100.00%> (-3.23%) |
:arrow_down: |
Flags with carried forward coverage won't be shown. Click here to find out more.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
Hey @sloede and @jlchan since you brought this up in #1195 can you take a brief look at this whether the fundamental choices (implementing this as a new set of equations) and form of temperature-dependent viscosity seem meaningful to you?
This looks good to me - I am only familiar with Sutherlands law though and not anything else more complex.
Thanks for working on this! I haven't looked at your changes in detail yet, but I'm wondering if really a second set of equations is required or desirable. Would it also be possible to implement this as a equation-of-state-like property of the original NSE implementation? That is, make the viscosity type a type parameter of the NSE struct, or even just allow passing different viscosity_dynamic
functions to the constructor of the NSE, allowing to either use a constant function, Sutherland's law, or something fancier?
I am worried about code duplication what I perceive (maybe wrongly) as an implementation detail in many other NSE codes I've seen. Maybe @andrewwinters5000 has a different take on this?
I haven't looked at your changes in detail yet, but I'm wondering if really a second set of equations is required or desirable. Would it also be possible to implement this as a equation-of-state-like property of the original NSE implementation? That is, make the viscosity type a type parameter of the NSE struct, or even just allow passing different
viscosity_dynamic
functions to the constructor of the NSE, allowing to either use a constant function, Sutherland's law, or something fancier?
That is a good idea, I will think about this. In principle, it should not be to difficult to pass in a function mu()
that is then being called with teperature. This way, one could also use more complex models.
Question is whether this can be done without (significant) performance loss for the constant viscosity case.
In principle, it should not be to difficult to pass in a function
mu()
that is then being called with teperature.
I would just pass the entire state such that the user can freely decide what to do with it.
Question is whether this can be done without (significant) performance loss for the constant viscosity case.
If a function is defined that ignores it's input and just returns a constant value, it should in virtually all cases just be optimized away.
A question remains though as to whether we can efficiently provide a convenience layer that auto-generates the constant function for a given numerical value of mu, such that a user may just pass the constant value and does not have to always wrap it in a function.
I am now using the same approach we employ for ICs and BCs.
There are two issues which a second or third pair of eyes should check:
Prandtl number datatype/function business
First, I removed the promote
part, i.e.,
μ, Pr = promote(mu, Prandtl)
and use Prandtl
all the time.
I am not sure why we currently pass in prandtl_number()
as a function,
https://github.com/trixi-framework/Trixi.jl/blob/ca082c2eb1273611cf38e80c6d2dab04e8f8177f/examples/tree_1d_dgsem/elixir_navierstokes_convergence_periodic.jl#L9
but then restrict it to being of RealT
https://github.com/trixi-framework/Trixi.jl/blob/ca082c2eb1273611cf38e80c6d2dab04e8f8177f/src/equations/compressible_navier_stokes_1d.jl#L93
This was originally brought in by @jlchan, maybe you can comment on this?
Also, there is still a TODO
floating around at every elixir
https://github.com/trixi-framework/Trixi.jl/blob/ca082c2eb1273611cf38e80c6d2dab04e8f8177f/examples/tree_1d_dgsem/elixir_navierstokes_convergence_periodic.jl#L8-L10
maybe now is the time to tackle this.
Temperature non-dimensionalization
The temperature $T$ gets computed as
https://github.com/trixi-framework/Trixi.jl/blob/ca082c2eb1273611cf38e80c6d2dab04e8f8177f/src/equations/compressible_navier_stokes_1d.jl#L246-L252
which follows from the ideal gas law
$$ p = \rho R_\text{specific} T$$
with certain non-dimensionalization.
Please have a look whether the re-dimensionalization as done in the new elixir is correct:
# Use Sutherland's law for a temperature-dependent viscosity
@inline function mu(u, equations)
T_ref = 291.15
R_specific_air = 287.052874
T = R_specific_air * Trixi.temperature(u, equations)
C_air = 120.0
mu0_air = 18.27e-6
return mu0_air * (T_ref + C_air) / (T + C_air) * (T / T_ref)^1.5
end
The results look good (in the eye norm) / reasonably similar to the version with constant viscosity.
I am not sure why we currently pass in
prandtl_number()
as a function
We don't. I think it was done to be consistent with mu()
, such that you can use the function also inside the initial condition
maybe now is the time to tackle this
This is a good job for a new student. However, I'd let them tackle this in a separate PR
The results look good (in the eye norm) / reasonably similar to the version with constant viscosity.
LGTM. But just to be sure, why not compute mu for a standard state (e.g., pressure and density at sea level, zero velocity) where you know what the actual value of mu
should be?
LGTM. But just to be sure, why not compute mu for a standard state (e.g., pressure and density at sea level, zero velocity) where you know what the actual value of
mu
should be?
Good idea, I will add a unit test or similar.
I am not sure why we currently pass in
prandtl_number()
as a functionI think it was done to be consistent with
mu()
, such that you can use the function also inside the initial condition
Ah okay.
I am not sure why we currently pass in
prandtl_number()
as a functionI think it was done to be consistent with
mu()
, such that you can use the function also inside the initial condition
I think this is the corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1503.
@jlchan Can you take a second look after the changes? And also maybe comment on https://github.com/trixi-framework/Trixi.jl/pull/1808#issuecomment-1980790651 ?
Will do - traveling at the moment but should have time to look at this tonight
Thanks for adding this, @DanielDoehring! Sorry the review is a bit late.
I am not sure why we currently pass in
prandtl_number()
as a function,
I believe this is because of https://github.com/trixi-framework/Trixi.jl/issues/1503. It's a function not because we treat it like one, but because we wanted to access it in a type stable manner in initial_condition
.
Also, there is still a TODO floating around at every elixir
Unifying the accessor function names for prandtl_number
and other parameters sounds good - but aren't they unified right now already? I'm not sure we can enforce anything, and since we have the keyword argument interface there are already suggestions for the accessor function names.
Unifying the accessor function names for
prandtl_number
and other parameters sounds good - but aren't they unified right now already? I'm not sure we can enforce anything, and since we have the keyword argument interface there are already suggestions for the accessor function names.
Yeah, they have at least the same datatype in the Navier-Stokes equation files. I removed the remaining 12 TODO
s.
Are we good to merge @JoshuaLampert and @sloede ?
A question remains though as to whether we can efficiently provide a convenience layer that auto-generates the constant function for a given numerical value of mu, such that a user may just pass the constant value and does not have to always wrap it in a function.
I lost a bit of track here. Is this convenience layer implemented, i.e. is it still possible to pass just a number?
Oops - I missed that. I don't think that has been added, but you could check the type of mu
and create an anonymous function if it's a number? Only question would be whether that is type stable or allocates.
Yes, but previously we passed in mu as a value. We now expect that mu is callable so this is technically a breaking change.
We can avoid this if we check that mu isa Real
then we could pass in (u, equations)->mu
to the constructor instead.
On Tue, Mar 19, 2024 at 10:20 AM Daniel Doehring @.***> wrote:
Not sure if I understand correctly, but in most cases we just pass in
mu(u, equations) = 0.01
which is non-allocating (test pass).
— Reply to this email directly, view it on GitHub https://github.com/trixi-framework/Trixi.jl/pull/1808#issuecomment-2006459038, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAI2HUG4AFNPGTIQEXKLORLYY77MXAVCNFSM6AAAAABCBXGAEOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMBWGQ2TSMBTHA . You are receiving this because you were mentioned.Message ID: @.***>
I guess we could also dispatch on mu
?
Ah I see.
Not sure if this is possible, I tried
#mu_f(u, equations) = mu
#mu_f = (u, equations) -> mu
function mu_f(u, equations)
return mu
end
CompressibleNavierStokesDiffusion1D{typeof(gradient_variables), typeof(gamma),
typeof(mu_f),
typeof(equations)}(gamma, inv_gamma_minus_one,
mu_f, Prandtl, kappa,
equations,
gradient_variables)
which gets, however, in every case immediately optimized away resulting in
ERROR: MethodError: objects of type Float64 are not callable
when calling mu = equations.mu(u, equations)
I guess we could change the signature of mu(u, equations) =
in the examples to mu(_, _) =
to shorten things.
What about adding a function like:
function CompressibleNavierStokesDiffusion1D(equations::CompressibleEulerEquations1D;
mu::Real, Prandtl,
gradient_variables = GradientVariablesPrimitive())
CompressibleNavierStokesDiffusion1D(equations, (_, _) -> mu, Prandtl, gradient_variables)
end
Does that work (I didn't test it)?
What I just tried, is:
julia> foo(mu, x) = mu(x)
foo (generic function with 1 method)
julia> foo(mu::Real, x) = foo(x -> mu, x)
foo (generic function with 2 methods)
julia> foo(1.0, 2.0)
1.0
julia> foo(x -> x^2, 2.0)
4.0
So I guess it should work (?)
Hm, what do we pick then for typeof(mu)
? This is the point I failed before as this gets recognized as Float
and not as a function.
Yeah, the optimization is kind of annoying:
julia> foo(mu, x) = println(typeof(mu))
foo (generic function with 1 method)
julia> foo(mu::Real, x) = foo(_ -> 1.0, x)
foo (generic function with 2 methods)
julia> foo(1.0, 2.0)
var"#3#4"
julia> foo(mu::Real, x) = foo(_ -> mu, x)
foo (generic function with 2 methods)
julia> foo(1.0, 2.0)
var"#5#6"{Float64}
Honestly, I think demanding six extra characters of mu(_, _) = 42
compared to mu = 42
is not an unbearable load that makes people turn away from Trixi.
Honestly, I think demanding six extra characters of mu(_, _) = 42 compared to mu = 42 is not an unbearable load that makes people turn away from Trixi.
The problem is that it is breaking. I would prefer not to introduce too many breaking changes if they are avoidable.
The problem is that it is breaking. I would prefer not to introduce too many breaking changes if they are avoidable.
Ah yeah right :+1: I will try the implementation with the if
-clause, that looks reasonable.