struct with evaluated/calculated attributes in Julia - julia

I am incredibly new to Julia, as in I downloaded it yesterday. I need to use structs for a project I am trying to start. I need my objects to consist of attributes that are part of the input and attributes that are calculated from that input. For example, I tried:
struct Bar
#Attributes from input
E::Float64
A::Float64
L::Float64
# Attributes calculated form input
k = (E*A/L)*[1.0 -1.0; -1.0 1.0]
end
but I get an error:
ERROR: LoadError: syntax: "k = (((E * A) / L) * [1 -1; -1 1])" inside type definition is reserved around Untitled-1:10
How should I go about achieving my desired outcome?

You don't need any special package or macro to do this. Just do it the standard way, in the constructor. The last struct attribute isn't some sort of special 'evaluated attribute', it's a completely ordinary attribute.
struct Bar
E::Float64
A::Float64
L::Float64
k::Matrix{Float64}
end
Bar(e, a, l) = Bar(e, a, l, (e*a/l)*[1 -1; -1 1])
jl> Bar(2,3,4)
Bar(2.0, 3.0, 4.0, [1.5 -1.5; -1.5 1.5])

You can try:
Base.#kwdef struct Bar
E::Float64
A::Float64
L::Float64
k::Matrix{Float64} = (E*A/L) .* [1.0 -1.0; -1.0 1.0]
end
and now:
julia> Bar(;E=1,A=2,L=3)
Bar(1.0, 2.0, 3.0, [0.6666666666666666 -0.6666666666666666; -0.6666666666666666 0.6666666666666666])
Important notes:
always define field type if possible - even when you set the default value for it
instead of Base.#kwdef you could try using Parameters package and the Parameters.#with_kw macro which is more powerful and has more options.

Related

Change one parameter value in structure does not affect related parameters

I have a mutable structure in Julia
#with_kw mutable struct Param
x::Float64 = 0.1
y::Float64 = 1/x
end
Now I want to change x to 0.2, so I set
para = Param() and para.x = 0.2. However, para.y does not change. But what I want is to change y automatically, is there any way in Julia to do this?
For now, Julia isn't so flexible in updating attributes based on other ones. But, gladfully, it's possible to simulate the expected behavior. For this, you need to define a custom getproperty function. Suppose I have defined a mutable composite type as follows:
julia> Base.#kwdef mutable struct Param
x::Float64=0.1
end
Param
Now I want to settle the situation to simulate the expected behavior. For this, I go for defining a custom getproperty function specifically for Param:
julia> function Base.getproperty(the_type::Param, prop::Symbol)
if prop == :y
return 1/the_type.x
end
return getfield(the_type, prop)
end
Now I create an instance of type Param holding the default value:
julia> instance = Param()
Param(0.1)
The output shows just the value of one field (x). But meanwhile, we can have the y property as we defined our custom getproperty function recently:
julia> instance.y
10.0
Now let's update the x field and observe if the y field gets updated automatically or not:
julia> instance.x = 0.2
0.2
julia> instance.y
5.0
Yes, it does!
Further setting
But still, I can't utilize the autocomplete ability of the REPL by hitting the Tab key to write instance.y. For this, I can define a custom propertynames function specifically for my composite type Param:
julia> Base.propertynames(the_type::Param) = (:x, :y)
julia> instance. #Hit Tab
x y
Now I can benefit from the autocomplete option in the REPL for y as well.
You are specifying the default value for a parameter, not an algorithmic relationship. If y is always supposed to be calculated like that and never set on it's own you can define
y(p::Param) = 1/p.x

Julia: Parametric types with inner constructor: new and typeof

Trying to understand parametric types and the new function available for inner methods. The manual states "special function available to inner constructors which created a new object of the type". See the section of the manual on new here and the section of the manual on inner constructor methods here.
Consider an inner method designed to calculate the sum of x, where x could be, say, a vector or a tuple, and is given the parametric type T. A natural thing to want is for the type of the elements of x to be inherited by their sum s. I don't seem to need new for that, correct?
struct M{T}
x::T
s
function M(x)
s = sum(x)
x,s
end
end
julia> M([1,2,3])
([1, 2, 3], 6)
julia> M([1.,2.,3.])
([1.0, 2.0, 3.0], 6.0)
julia> typeof(M([1.,2.,3.]))
Tuple{Vector{Float64}, Float64}
Edit: Correction! I intended to have the last line of the inner constructor be M(x,s)... It's still an interesting question, so I won't correct it. How does M(x,s) differ from new{typeof(x)}(x,s)?
One usage of new I have seen is in combination with typeof(), something like:
struct M{T}
x::T
s
function M(x)
s = sum(x)
new{typeof(x)}(x,s)
end
end
julia> M([1,2,3])
M{Vector{Int64}}([1, 2, 3], 6)
julia> M([1.,2.,3.])
M{Vector{Float64}}([1.0, 2.0, 3.0], 6.0)
What if wanted to constrain s to the same type as x? That is, for instance, if x is a vector, then s should be a vector (in this case, a vector of one element). How would I do that? If I replace the last line of the inner constructor with x, new{typeof(x)}(s), I get the understandable error:
MethodError: Cannot `convert` an object of type Int64 to an object of type Vector{Int64}
Here are the rules:
If you are writing an outer constructor for a type M, the constructor should return an instance of M by eventually calling the inner constructor, like this: M(<args>).
If you are writing an inner constructor, this will override the default inner constructor. So you must return an instance of M by calling new(<args>).
The new "special function" exists to allow the construction of a type that doesn't have a constructor yet. Observe the following example:
julia> struct A
x::Int
function A(x)
A(x)
end
end
julia> A(4)
ERROR: StackOverflowError:
Stacktrace:
[1] A(::Int64) at ./REPL[3]:4 (repeats 79984 times)
This is a circular definition of the constructor for A, which results in a stack overflow. You cannot pull yourself up by your bootstraps, so Julia provides the new function as a way to circumvent this problem.
You should provide the new function with a number of arguments equal to the number of fields in your struct. Note that the new function will attempt to convert the types of its inputs to match the declared types of the fields of your struct:
julia> struct B
x::Float64
B(x) = new(x)
end
julia> B(5)
B(5.0)
julia> B('a')
B(97.0)
julia> B("a")
ERROR: MethodError: Cannot `convert` an object of type String to an object
of type Float64
(The inner constructor for B above is exactly the same as the default inner constructor.)
When you're defining parametric types, the new function must be provided with a number of parameters equal to the number of parameters for your type (and in the same order), analogously to the default inner constructor for parametric types. First observe how the default inner constructor for parametric types is used:
julia> struct Foo{T}
x::T
end
julia> Foo{String}("a")
Foo{String}("a")
Now if you were writing an inner constructor for Foo, instead of writing Foo{T}(x) inside the constructor, you would replace the Foo with new, like this: new{T}(x).
You might need typeof to help define the constructor, but often you don't. Here's one way you could define your M type:
struct M{I, T}
x::I
s::T
function M(x::I) where I
s = sum(x)
new{I, typeof(s)}(x, s)
end
end
I'm using typeof here so that I could be any iterable type that returns numbers:
julia> typeof(M(1:3))
M{UnitRange{Int64},Int64}
julia> g = (rand() for _ in 1:10)
Base.Generator{UnitRange{Int64},var"#5#6"}(var"#5#6"(), 1:10)
julia> typeof(M(g))
M{Base.Generator{UnitRange{Int64},var"#5#6"},Float64}
Note that providing the parameters for your type is required when you are using new inside an inner constructor for a parametric type:
julia> struct C{T}
x::Int
C(x) = new(x)
end
ERROR: syntax: too few type parameters specified in "new{...}" around REPL[6]:1
Remember, a constructor is designed to construct something. Specifically, the constructor M is designed to construct a value of type M. Your example constructor
struct M{T}
x::T
s
function M(x)
s = sum(x)
x,s
end
end
means that the result of evaluating the expression M([1 2 3]) is a tuple, not an instance of M. If I encountered such a constructor in the wild, I'd assume it was a bug and report it. new is the internal magic that allows you to actually construct a value of type M.
It's a matter of abstraction. If you just want a tuple in the first place, then forget about the structure called M and just define a function m at module scope that returns a tuple. But if you intend to treat this as a special data type, potentially for use with dynamic dispatch but even just for self-documentation purposes, then your constructor should return a value of type M.

Why is Ada not trapping this specified range check?

I am going through the learn.adacore.com tutorial and have hit upon a problem that I am unsure of.
Specifically I understand that Ada is designed to trap attempts to overflow a variable with a specified range definition.
In the case below, the first attempt to do this causes a compiler 'range check failure' which is expected. However the following line doesn't trap it and I am not sure why:
with Ada.Text_IO; use Ada.Text_IO;
procedure Custom_Floating_Types is
type T_Norm is new float range -1.0 .. 1.0;
D : T_Norm := 1.0;
begin
Put_Line("The value of D =" & T_Norm'Image(D));
-- D := D + 1.0; -- This causes a range check failure at run time = completely expected.
Put_Line("The value of D =" & T_Norm'Image(D + 1.0)); -- This doesn't?
end Custom_Floating_Types;
You have a couple of pretty good answers, but I'm going to add another because it's clear that you expect the expression D + 1.0 to raise an exception, and the answers you have don't explain why it doesn't.
A type declaration like
type T_Norm is new float range -1.0 .. 1.0;
is roughly equivalent to
type T_Norm'Base is new Float;
subtype T_Norm is T_Norm'Base range -1.0 .. 1.0;
The type (called the "base type") isn't given a name, though it can often be referenced with the 'Base attribute. The name that is given is to a subtype, called the "first-named subtype".
This distinction is important and is often not given enough attention. As explained by egilhh, T_Norm'Image is defined in terms of T_Norm'Base. This is also true of the arithmetic operators. For example, "+" is defined as
function "+" (Left : in T_Norm'Base; Right : in T_Norm'Base) return T_Norm'Base;
2.0 is clearly in the range of T_Norm'Base, so evaluating D + 1.0 doesn't violate any constraints, nor does passing it to T_Norm'Image. However, when you try to assign the resulting value to D, which has subtype T_Norm, a check is performed that the value is in the range of the subtype, and an exception is raised because the check fails.
This distinction is used in other places to make the language work reasonably. For example, a constrained array type
type A is array (1 .. 10) of C;
is roughly equivalent to
type A'Base is array (Integer range <>) of C;
subtype A is A'Base (1 .. 10);
If you do
V : A;
... V (2 .. 4)
you might expect problems because the slice doesn't have the bounds of A. But it works because the slice doesn't have subtype A but rather the anonymous subtype A'Base (2 ..4).
The definition of 'Image says:
For every scalar subtype S:
S'Image denotes a function with the following specification:
function S'Image(Arg : S'Base)
return String
As you can see, it takes a parameter of the base (unconstrained) type
T_Norm'Image(D + 1.0) neither assigns nor reads an out-of-range value. It asks for the 'Image attribute (string representation) of (D + 1.0), which is the same as asking for (1.0 + 1.0).
I can see two ways the confusion might arise. First, the name "attribute" could be misinterpreted to suggest that 'Image involves something intrinsic to D. It doesn't. The 'Image attribute is just a function, so D is just part of the expression that defines the value of the parameter (in your example = 2.0).
Second, the 'Image attribute comes from Float. Thus, any Float can be its parameter.
You can create a function that accepts only parameters of T_Norm'Range and make that function an attribute of T_Norm. See Ada Reference Manual 4.10.
Because you don't store a value greater than range in D.

Update this statement for Julia 1.0

Could someone explain how to update this for Julia 1.0
function _encode_zigzag{T <: Integer}(n::T)
num_bits = sizeof(T) * 8
(n << 1) ⊻ (n >> (num_bits - 1))
end
And also what is the difference with:
function _encode_zigzag(n::Integer)
num_bits = sizeof(T) * 8
(n << 1) ⊻ (n >> (num_bits - 1))
end
Firstly, in Julia 1.x the subtype constraints on type parameters are stated after the parameters and followed by the reserved word where.
function _encode_zigzag(n::T) where {T <: Integer}
num_bits = sizeof(T) * 8
(n << 1) ⊻ (n >> (num_bits - 1))
end
The curly braces are unnecessary when there is only one type parameter but it is recommended to keep for clarity.
Now for the second question. In the version of your method where n is an Integer, sizeof will not work since the size of an abstract type is undefined. In this case, establishing the subtype constraint helps make sure that the given argument will have a defined size while still giving flexibility for different types. Julia will compile different versions of the function; one for each Integer subtype that gets passed.
This is more efficient than declaring the function with n having a concrete type like Int64, since this would mean the argument would have to be converted to the same type before executing the function.
You can read more of this in the Julia documentation.

How can I shorten a type in the body of a function

Best be explained by an example:
I define a type
type myType
α::Float64
β::Float64
end
z = myType( 1., 2. )
Then suppose that I want to pass this type as an argument to a function:
myFunc( x::Vector{Float64}, m::myType ) =
x[1].^m.α+x[2].^m.β
Is there a way to pass myType so that I can actually use it in the body of the function in a "cleaner" fashion as follows:
x[1].^α+x[2].^β
Thanks for any answer.
One way is to use dispatch to a more general function:
myFunc( x::Vector{Float64}, α::Float64, β::Float64) = x[1].^α+x[2].^β
myFunc( x::Vector{Float64}, m::myType = myFunc(x,m.α,m.β)
Or if your functions are longer, you may want to use Parameters.jl's #unpack:
function myFunc( x::Vector{Float64}, m::myType )
#unpack m: α,β #now those are defined
x[1].^α+x[2].^β
end
The overhead of unpacking is small because you're not copying, it's just doing a bunch of α=m.α which is just making an α which points to m.α. For longer equations, this can be a much nicer form if you have many fields and use them in long calculations (for reference, I use this a lot in DifferentialEquations.jl).
Edit
There's another way as noted in the comments. Let me show this. You can define your type (with optional kwargs) using the #with_kw macro from Parameters.jl. For example:
using Parameters
#with_kw type myType
α::Float64 = 1.0 # Give a default value
β::Float64 = 2.0
end
z = myType() # Generate with the default values
Then you can use the #unpack_myType macro which is automatically made by the #with_kw macro:
function myFunc( x::Vector{Float64}, m::myType )
#unpack_myType m
x[1].^α+x[2].^β
end
Again, this only has the overhead of making the references α and β without copying, so it's pretty lightweight.
You could add this to the body of your function:
(α::Float64, β::Float64) = (m.α, m.β)
UPDATE: My original answer was wrong for a subtle reason, but I thought it was a very interesting bit of information so rather than delete it altogether, I'm leaving it with an explanation on why it's wrong. Many thanks to Fengyang for pointing out the global scope of eval! (as well as the use of $ in an Expr context!)
The original answer suggested that:
[eval( parse( string( i,"=",getfield( m,i)))) for i in fieldnames( m)]
would return a list comprehension which had assignment side-effects, since it conceptually would result in something like [α=1., β=2., etc]. The assumption was that this assignment would be within local scope. However, as pointed out, eval is always assessed at global scope, therefore the above one-liner does not do what it's meant to. Example:
julia> type MyType
α::Float64
β::Float64
end
julia> function myFunc!(x::Vector{Float64}, m::MyType)
α=5.; β=6.;
[eval( parse( string( i,"=",getfield( m,i)))) for i in fieldnames( m)]
x[1] = α; x[2] = β; return x
end;
julia> myFunc!([0.,0.],MyType(1., 2.))
2-element Array{Float64,1}:
5.0
6.0
julia> whos()
MyType 124 bytes DataType
myFunc 0 bytes #myFunc
α 8 bytes Float64
β 8 bytes Float64
I.e. as you can see, the intention was for the local variables α and β to be overwritten, but they didn't; eval placed α and β variables at global scope instead. As a matlab programmer I naively assumed that eval() was conceptually equivalent to Matlab, without actually checking. Turns out it's more similar to the evalin('base',...) command.
Thanks again to Fengyand for giving another example of why the phrase "parse and eval" seems to have about the same effect on Julia programmers as the word "it" on the knights who until recently said "NI". :)

Resources