-
Notifications
You must be signed in to change notification settings - Fork 10
Convert ErtelPotentialVorticity and its thermal-wind counterpart into a Diagnostic
#194
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
base: main
Are you sure you want to change the base?
Conversation
Another approach to this might implement the kernel functions as callable objects, eg struct ErtelPVKernelFunction{thermal_wind, C}
tracer :: C
end
const ErtelPV = KernelFunctionOperation{<:Any, <:Any, <:Any, <:Any, <:Any, <:ErtelPVKernelFunction}
(pv::ErtelPVKernelFunction{false}(i, j, k, grid, args...) = # whatever needs to be
(pv::ErtelPVKernelFunction{true}(i, j, k, grid, args...) = # whatever needs to be Then you can provide constructors like function ErtelPV(model, thermal_wind=true)
kernel_function = ErtelPVKernelFunction{thermal_wind, typeof(tracer)}(tracer)
return KernelFunctionOperation{Face, Face, Face}(grid, kernel_function, args...)
end This is a strategy of extension rather than building a wrapper, which may be simpler and also easier for users to understand. |
That's an interesting approach, but it has the downside that users don't easily know whether the PV that's being calculated is in thermal or not, right? They'd need to investigate the function that's used in the kernel. |
Huh. Types are easier, not harder. Functions are a bit more annoying. Not only for the function itself but also the operation. For example: const ThermalWindErtelPVKernelFunction = ErtelPVKernelFunction{true}
const ErtelPVOperation = KernelFunctionOperation{<:Any, <:Any, <:Any, <:Any, <:Any, ErtelPVKernelFunction}
const ThermalWindErtelPVOperation = KernelFunctionOperation{<:Any, <:Any, <:Any, <:Any, <:Any, ThermalWindErtelPVKernelFunction} That said I don't know what tools you are providing the user to understand what operations are being used. But also I don't know if this matters; at least at the REPL we are using In other words there is no downside in terms of functionality or user interface. To a first approximation they are the same, in the details; explicit types are easier to work with than function-types. There is a bit more flexibility with callable objects because embedded data/parameters do not need to be There are downsides or we would always use callback objects instead of functions. The downside is the more complex implementation, requiring both a struct and a function. But I would also argue that the functionality being implemented is pretty complex already. |
Sorry, I'm not sure I fully understand the pros and cons here. In both cases you have to introduce new |
I can't remember all the reasoning on the thread but one way to think about it is that types should be used to represent concepts and functions represent actions or verbs. Here it seems we are really dealing with types, ie you want an object that embodies ErtelPotentialVorticity. You also want to compute it, but that happens under the hood really, and the functional interface for that is implemented in Oceananigans. We do get an object here in Technically you can achieve type alias using the fact that functions are types too but I think its cleaner and more explicit to use a struct. |
Agreed.
Sorry, that's where I get a bit confused. The way I understand it, you're proposing this construct: struct ErtelPVKernelFunction{thermal_wind, C}
tracer :: C
end
const ErtelPV = KernelFunctionOperation{<:Any, <:Any, <:Any, <:Any, <:Any, <:ErtelPVKernelFunction} right? So that when the user calls In my view it's better to define a struct for the operation itself that either wraps around or uses struct ErtelPotentialVorticity <: AbstractDiagnostic
operation # In this case, this would be a KernelFunctionOperation
thermal_wind :: Bool
tracer
end where in this case Not sure what's best, but my idea is to do it in a way that all diagnostics provided by Oceanostics, whether they use It may be that your suggestion is still a better way to do things, I'm not sure. I just wanna make sure we're on the same page before moving on. |
As another example, under what I'm proposing, struct KineticEnergyDissipationRate <: AbstractDiagnostic
operation
isotropic_viscosity :: Bool
end which would condense both |
But then you need to reimplement the entire interface for |
So no, I disagree. I think conceptually, your idea represents a new "kernel function". And the best way to implement this in Julia is indeed by implementing a new kernel function, and then using the type parameter of This is the idea: struct OuterType{T}
inner :: T
end
struct FunInnerType{V}
value :: V
end
const FunOuterType = OuterType{<:FunInnerType}
FunOuterType(val) = OuterType(FunInnerType(val))
function Base.show(io::IO, fot::FunOuterType)
V = typeof(fot.inner.value)
print(io, string("FunOuterType{$V}(", fot.inner.value, ")"))
end
fun_one = FunOuterType(1) julia> include("alias.jl")
FunOuterType{Int64}(1) |
This PR serves two purposes.
ErtelPotentialVorticity
andThermalWindPotentialVorticity
.AbstractDiagnostic
type, and again I'm trying this out for PV first.This is what it's looking like for PV:
Oceanostics.jl/src/FlowDiagnostics.jl
Lines 247 to 251 in 0d18434
The idea is still a bit abstract in my head, so I'd appreciate some feedback. But basically
AbstractDiagnostic
would allow other types of operation (some types that don't fit the scope ofKernelFunctionOperation
) to become the same type, so that users can interact with them in the same way. For example, the show method forErtelPV
whenthermal_wind=true
would be something more friendly:and we'd also have access to the tracer used in the PV definition.
Although at the moment there isn't much practical difference, I can envision this being more important as we move towards reorganizing Oceanostics by budget equations (see #138 and #35), where it would make it easy to keep track of assumptions that get be inherited from budget equations to each of their terms (again, in the future).