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

Constructor with units #305

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

SebastianRuffert
Copy link
Contributor

No description provided.

@SebastianRuffert SebastianRuffert marked this pull request as draft June 15, 2022 07:01
@SebastianRuffert
Copy link
Contributor Author

This is a very straightforward way to implement units. Do we want to allow for units in both constructor functions or only the one I changed so far?

@SebastianRuffert
Copy link
Contributor Author

Also I am unsure if we should convert all default values to the user-specified unit, if only one keyword argument is given

Comment on lines 74 to 84
function Box(CO, hX::Quantity, hY::Quantity, hZ::Quantity, origin, rotation)
_hX = to_internal_units(hX)
_hY = to_internal_units(hY)
_hZ = to_internal_units(hZ)
Box(CO, _hX, _hY, _hZ, origin, rotation)
end

function Box(::Type{CO}=ClosedPrimitive;
hX = 1,
hY = 1,
hZ = 1,
hX = 1u"m",
hY = 1u"m",
hZ = 1u"m",
Copy link
Collaborator

Choose a reason for hiding this comment

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

SSD does not know which function to call.
Try to specify in the already existing function above this one that TX, TY and TZ can only be a Real.

@@ -70,6 +70,15 @@ function Box(CO, hX::TX, hY::TY, hZ::TZ, origin::PT, rotation::ROT) where {TX, T
Box{T}(CO,T(hX), T(hY), T(hZ), origin, rotation)
end

#Unit convesion
Box(CO, hX::Quantity, hY, hZ, origin, rotation) = Box(CO, to_internal_units(hX), hY, hZ, origin, rotation)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a convenient way to write those permutations?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Use RealQuantity?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is RealQuantity part of Unitful? I can not find it

Copy link
Collaborator

Choose a reason for hiding this comment

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

RealQuantity is part of SSD:

const RealQuantity = MaybeWithUnits{<:Real}

Comment on lines 75 to 80
Box(CO, hX, hY::Quantity, hZ, origin, rotation) = Box(CO, hX, to_internal_units(hY), hZ, origin, rotation)
Box(CO, hX, hY, hZ::Quantity, origin, rotation) = Box(CO, hX, hY, to_internal_units(hZ), origin, rotation)
Box(CO, hX::Quantity, hY::Quantity, hZ, origin, rotation) = Box(CO, to_internal_units(hX), to_internal_units(hY), hZ, origin, rotation)
Box(CO, hX, hY::Quantity, hZ::Quantity, origin, rotation) = Box(CO, hX, to_internal_units(hY), to_internal_units(hZ), origin, rotation)
Box(CO, hX::Quantity, hY, hZ::Quantity, origin, rotation) = Box(CO, to_internal_units(hX), hY, to_internal_units(hZ), origin, rotation)
Box(CO, hX::Quantity, hY::Quantity, hZ::Quantity, origin, rotation) = Box(CO, to_internal_units(hX), to_internal_units(hY), to_internal_units(hZ), origin, rotation)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not a fan of these permutations...

What is the problem here and why doesn't this work without permutations?
to_internal_units also works for numbers without units and just returns the number:

This could be fixed by RealQuantity but I see that RealQuantity is only defined in SSD and not CSG and we are modifying CSG functions. Maybe it makes sense to define RealQuantity in CSG already and export it?

Then, the following might (?) work:

function Box(CO, hX::RealQuantity, hY::RealQuantity, hZ::RealQuantity, origin, rotation)
    _hX = to_internal_units(hX)
    _hY = to_internal_units(hY)
    _hZ = to_internal_units(hZ)
    Box(CO, _hX, _hY, _hZ, origin, rotation)
end

@@ -109,6 +109,8 @@ end
@inline _convert_point(pt::AbstractCoordinatePoint, ::Type{Cylindrical}) = CylindricalPoint(pt)
@inline _convert_point(pt::AbstractCoordinatePoint, ::Type{Cartesian}) = CartesianPoint(pt)

scale(cart::CartesianPoint{T}, fact) where T = CartesianPoint{T}(cart.x*fact, cart.y*fact, cart.z*fact)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would be careful with this!
If we look at the docstring, CartesianPoint will always be saved in units of m.
I would prefer to have a constructor with units for CartesianPoint and solve this via the constructor and not with scale..

Box{T}(CO, hX, hY, hZ, origin, rotation)
) where {T, CO, UN<:Unitful.Units}
unit_factor = ustrip(uconvert(unt,1u"m"))
Box{T}(CO, hX/unit_factor, hY/unit_factor, hZ/unit_factor, scale(CartesianPoint{T}(origin),1/unit_factor), rotation)
Copy link
Collaborator

Choose a reason for hiding this comment

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

(origin) here does not need brackets, but as said above: maybe there is another way of implementing this.

@lmh91
Copy link
Collaborator

lmh91 commented Jun 28, 2022

I don't think we should do this like that.

Let's take just the CartesianPoint as an example (its the same for everything else including the primitives):

CartesianPoint(1u"m", 2u"m", 3u"m")

should return CartesianPoint{promote_type(typeof.(x, y, y))} (pseudo code) and definitely not convert
to CartesianPoint{Int/Float}.

Instead we have to add methods to Unitful.ustrip and Unitful.uconvert for our types.
uconvert might be a bit difficult as our types have different fields. I am not sure if there is a general syntax for that in Unitful.jl.
Maybe there is also a function like unify_units in Unitful.jl for cases like that. Or this should be handled in the promotion.
E.g. (1u"m", 2u"mm)" will be promoted to (1.0u"m", 0.002u"m").
We have to look into Unitful.jl if there are already some conventions for this.

If not, we could (in SSD but not in the submodule CSG), define some _ssd_uconvert-methods for our types and
then just ustrip them with the ustrip methods which we can define in the CSG submodule.

@lmh91
Copy link
Collaborator

lmh91 commented Jun 28, 2022

I just tested on the current main branch:

julia> CartesianPoint(1u"m", 2u"m", 3u"m")
3-element CartesianPoint{Quantity{Int64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}} with indices SOneTo(3):
 1 m
 2 m
 3 m

That works already out of the box as we do not limit T for AbstractGeometry.

julia> CartesianPoint(1u"m", 2u"m", 3u"mm")
3-element CartesianPoint{Quantity{Rational{Int64}, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}} with indices SOneTo(3):
    1//1 m
    2//1 m
 3//1000 m

And there seems also already to be some promotion rules defined in Unitful.jl as the second case also works :)

Copy link
Collaborator

@lmh91 lmh91 left a comment

Choose a reason for hiding this comment

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

Hmm, this topic is a bit tricky.

In principle, we (Julia ecosystem) don't want to convert a Quantity by default to its underlying type. This is should be done by implementing a method for ustrip for Box.
So a primitive with units could be constructed (and the information of the units
are still attached to the primitive and not removed like it is implemented right now).

In order to do this we would need to change the structs of the primitives though.
We would need to separate the type of the rotation from the types of the other fields
like hX:

struct Box{T,CO,RT} <: AbstractVolumePrimitive{T,CO}
    hX::T # so `T` could also be a Quantity here
    hY::T
    hZ::T
    origin::CartesianPoint{T}
    rotation::SMatrix{3,3,RT,9}
end

We also would need to change the definition of the CylindricalPoint by the way
as we will need different types for φ and r&z.

This would be in principle the best (julianic) way in my opinion.
However, it requires the most work as well and make things a bit more complicated (more parametric), though not impossible.

Or, we force the ustrip in the construction and make it impossible to construct primitives with units still attached like it is the design in this PR right now.
But, then we should also do this for the point types like CartesianPoint. For consistency.
And for the points, I would really like to have them with units attached 🤔

Hmm...

_csg_convert_args(eltype::Type{T}, r::Tuple) where T = broadcast(x -> _csg_convert_args(T, x), r)
_csg_convert_args(eltype::Type{T}, r::Nothing) where T = nothing
_csg_convert_args(eltype::Type{T}, r::Quantity) where T = convert(T,ustrip(uconvert(u"m",r)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This only works for LengthQuantity, not for angles etc.

@SebastianRuffert SebastianRuffert marked this pull request as draft August 4, 2022 07:52
_precision_type(::Type{T}) where {T <: Real} = T
_precision_type(::Type{Quantity{T}}) where {T <: Real} = T

float(::Type{Quantity{T}}) where T = T
Copy link
Collaborator

Choose a reason for hiding this comment

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

Careful! This is type piracy!
Quantity is a type that SSD does not own, so we should be VERY careful when extending the Base.float method

test/runtests.jl Outdated
Comment on lines 16 to 22
# @testset "Comparison to analytic solutions" begin
# include("comparison_to_analytic_solutions.jl")
# end
#
# @testset "SOR GPU Backend" begin
# include("SOR_GPU_Backend.jl")
# end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't exclude tests without mentioning why you exclude them.

@@ -38,7 +38,7 @@ module ConstructiveSolidGeometry
const CartesianTicksTuple{T} = NamedTuple{(:x,:y,:z), NTuple{3,Vector{T}}}
const CylindricalTicksTuple{T} = NamedTuple{(:r,:φ,:z), NTuple{3,Vector{T}}}

abstract type AbstractGeometry{T <: AbstractFloat} end
abstract type AbstractGeometry{T <: Number} end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why Number and not AbstractFloat?

@SebastianRuffert SebastianRuffert marked this pull request as ready for review August 16, 2022 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants