Trixi.jl icon indicating copy to clipboard operation
Trixi.jl copied to clipboard

Sutherlands Law for temperature dependent viscosity

Open DanielDoehring opened this issue 1 year ago • 14 comments

Closes #1195

DanielDoehring avatar Jan 19 '24 10:01 DanielDoehring

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.

github-actions[bot] avatar Jan 19 '24 10:01 github-actions[bot]

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.

codecov[bot] avatar Jan 19 '24 11:01 codecov[bot]

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?

DanielDoehring avatar Mar 01 '24 08:03 DanielDoehring

This looks good to me - I am only familiar with Sutherlands law though and not anything else more complex.

jlchan avatar Mar 02 '24 00:03 jlchan

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?

sloede avatar Mar 02 '24 06:03 sloede

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.

DanielDoehring avatar Mar 02 '24 08:03 DanielDoehring

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.

sloede avatar Mar 02 '24 09:03 sloede

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.

DanielDoehring avatar Mar 06 '24 12:03 DanielDoehring

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?

sloede avatar Mar 07 '24 07:03 sloede

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.

DanielDoehring avatar Mar 07 '24 08:03 DanielDoehring

I am not sure why we currently pass in prandtl_number() as a function

I think it was done to be consistent with mu(), such that you can use the function also inside the initial condition

Ah okay.

DanielDoehring avatar Mar 07 '24 08:03 DanielDoehring

I am not sure why we currently pass in prandtl_number() as a function

I 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.

JoshuaLampert avatar Mar 07 '24 09:03 JoshuaLampert

@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 ?

DanielDoehring avatar Mar 14 '24 09:03 DanielDoehring

Will do - traveling at the moment but should have time to look at this tonight

jlchan avatar Mar 14 '24 16:03 jlchan

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.

jlchan avatar Mar 17 '24 20:03 jlchan

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 TODOs.

DanielDoehring avatar Mar 18 '24 09:03 DanielDoehring

Are we good to merge @JoshuaLampert and @sloede ?

andrewwinters5000 avatar Mar 19 '24 08:03 andrewwinters5000

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?

JoshuaLampert avatar Mar 19 '24 08:03 JoshuaLampert

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.

jlchan avatar Mar 19 '24 09:03 jlchan

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: @.***>

jlchan avatar Mar 19 '24 09:03 jlchan

I guess we could also dispatch on mu?

JoshuaLampert avatar Mar 19 '24 10:03 JoshuaLampert

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)

DanielDoehring avatar Mar 19 '24 10:03 DanielDoehring

I guess we could change the signature of mu(u, equations) = in the examples to mu(_, _) = to shorten things.

DanielDoehring avatar Mar 19 '24 10:03 DanielDoehring

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)?

JoshuaLampert avatar Mar 19 '24 10:03 JoshuaLampert

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 (?)

JoshuaLampert avatar Mar 19 '24 10:03 JoshuaLampert

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.

DanielDoehring avatar Mar 19 '24 10:03 DanielDoehring

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}

JoshuaLampert avatar Mar 19 '24 10:03 JoshuaLampert

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.

DanielDoehring avatar Mar 19 '24 10:03 DanielDoehring

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.

JoshuaLampert avatar Mar 19 '24 10:03 JoshuaLampert

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.

DanielDoehring avatar Mar 19 '24 12:03 DanielDoehring