Dramatic performance change by adding additional unused method

Thanks for fixing my poor examples :slight_smile:

1 Like

Thanks for your suggestion but I still have a problem with the second part, which is the nested structures that are coded with PlacedVolume.

struct Mother{T,S,M,PV}
    label::String
    shape::S             # Reference to the actual shape
    material::M                  # Reference to material
    daughters::Vector{PV} 
end

struct PlacedVolume{T,....}
    transformation::Transformation3D{T}
    volume::Mother{T,S,M,PlacedVolume{T,...}}
end

I do not know what to put in the .... At a given moment I must break the ‘concreteness’ of the geometry. I cannot parametrize a mother volume with all the concrete types of the daughters, and the daughters of the daughters. I can do it for example as I did, at the level of the Mother having a member that is abstract (the shape), or I could do it a the Vector{PV} by having a Vector{AbstractPlacement}, but I would guess I will end with the same performance problems. Your help will be most welcome to find the right pattern.

1 Like

In terms of expressing it, you can just use the same pattern again:

struct PlacedVolume{T,M}
    transformation::T
    volume::M
end

and then putting constraints inside the braces, if you want. That will clean up the proliferation of parameters.

But this definition becomes mutually recursive. I actually don’t know how well the compiler handles that. You can try?

1 Like

you don’t have to enforce the correctness of this with typing system, you could enforce it with the constructor function and simply leave it as what DNF has suggested. Of course, you could always work some of the constraint into the type system if it’s natural and easy to do, but you don’t have to.

If you want polymorphism and performance (both runtime and compile time), I’d suggest trying a package like Unityper or doing it manually via enums.

2 Likes

My brain is exploding because of the recursivelity. The problem is that I have a Vector of different shaped ‘daughters’. Either, each volume is the same regardless of the shape (what I did) and then I have an uniform Vector, or I do have a different volume for each type of shape and I do have an heterogenous Vector. Clearly I cannot express in a constructor all these complexity. My guess is that having an heterogenous Vector will boil down to the same poor performance.

1 Like

BTW, this thread might be relevant.

I tried with having a Vector of abstract types. Like this:

abstract type AbstractPlacement{T<:AbstractFloat} end

#---Volume-------------------------------------------------------------------------
struct Volume{T<:AbstractFloat, S<:AbstractShape{T}, M<:AbstractMaterial}
    label::String
    shape::S                            # Reference to the actual shape
    material::M                         # Reference to material
    daughters::Vector{AbstractPlacement{T}}
end

#---PlacedVolume-------------------------------------------------------------------
struct PlacedVolume{T<:AbstractFloat,S<:AbstractShape{T}, M<:AbstractMaterial} <: AbstractPlacement{T}
    transformation::Transformation3D{T}
    volume::Volume{T,S,M}
end

Volume{T}(label::String, shape::S, material::M) where {T,S,M} = Volume{T,S,M}(label, shape, material, Vector{AbstractPlacement{T}}())

The performance is twice worst than what I had originally.

Are you sure you’re not trying to express too much? Most of the time the compiler can figure out the types. Can’t you just use a type parameter, and leave it to the compiler to find out what it is? Both a homogeneous and a heterogeneous vector can be expressed as Vector{T} for some T.

1 Like

Sorry, perhaps I am not expressing myself well. In the system I developing I would be ending with about 20 different ‘shapes’ with more of less deep hierarchies. As I said, I can either have a unique Volume that encapsulates a polymorphic shape and then have a homogenous vector of all the daughter volumes (this was the original design). Or, have a different Volume type for each shape and have an heterogenous vector of daughters. I do not see how I can completely avoid polymorphism.

How heavy would be to change the max_methods from 3 to something like 20?

What I’m saying is that with this type specification

it is impossible to get the benefits when the vector genuinely is homogeneous. You are forcing the element type to always be abstract, even when it’s not necessary. If you instead use

daughters::Vector{T} # with T<: AbstractPlacement 

you allow a concrete eltype when possible, and an abstract type when necessary.

yes, but each eltype is different in general. A ‘mother’ volume may have ‘boxes’, ‘tubes’, ‘spheres’, etc. as daughters.

Should work anyway. What am I missing?

Vector{T} is a vector of ‘anything’, including arbitrarily nested types, type unions, or abstract types.

1 Like

Is this the typocalypse?

1 Like

For this kind of low-level polymorphism I would suggest moving the typing from the Julia type system to your own data. In other words, I suggest you create a single type that has as many Float64 “point” fields as you may need for your largest shape, and a single tag field which the type is an @enum of your 20 primitive shapes. Then write a single area function that has a long if ...; ...; elseif ...; ...; end that deals with your 20 possible cases. This is not easily extensible by third parties but will give you some idea of how fast your system could possible be.

2 Likes

Unityper, which I linked to before, tries to provide a more convenient API for this, and tries to compactify the final struct in the case where not all their fields are the same.

But you can also do it manually.
I would strongly recommend something like this approach.

2 Likes

Sorry if I am very slow to understand. I see that Vector{T} can be a vector of anything. But what I need is a vector that the elements are of different types all of them inheriting from some Abstract type.
In languages such as C++ this will be implemented with an abstract class (with pure visual methods). I start understanding that the Abstract type in Julia is not equivalent (as it is not used to define an API) and is only used as a pre-condition for argument checking.

Thanks. But honestly I was thinking that Julia would provide better help for this. This is equivalent of C or even lower, since there is no switch statement in Julia.
I was expecting that multi-dispatch would help me. I do have a reference to a AbstractShape and the proper method is called for each of the shapes. What I cannot understand is why the compiler cannot infer what are return values, even if I specify them in the signature of the function. The other thing I do not understand is why there is memory allocation involved in the dispatching.

Yes, and Vector{T} can express that. My point is that parameterizing like that leaves room either for a totally heterogeneous vector or for a homogeneous vector of concrete eltype.