Skip to content

Commit

Permalink
Get rid of ObjectGroup and use Object also for groups
Browse files Browse the repository at this point in the history
It's better to manage a single type, because of how things work
down the line, especially when creating NamedTuples with Object values.
In particular, an array of NamedTuples will only have the 'right'
type if all the values for a given key have the same type.
So, if for some of the elements, the key `:node` has a value of type
`Object` and for some others `ObjectGroup`, julia won't be able to
determine the 'right' element type for the array and some of our
overrides in SpineOpt won't work.

We also introduce `members` to replace `expand`, and `groups` to
return an array of all `Object`s where an `Object` passed as
argument happens to be a member.
  • Loading branch information
Manuel Marin committed Jul 1, 2020
1 parent 22017bb commit 2c1ba45
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 57 deletions.
2 changes: 2 additions & 0 deletions src/SpineInterface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export using_spinedb
export anything
export indices
export write_parameters
export members
export groups
export blocks
export duration
export start
Expand Down
7 changes: 7 additions & 0 deletions src/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ function (p::Parameter)(;_strict=true, prefix=(), i=nothing, t=nothing, kwargs..
nothing
end

members(::Anything) = anything
members(x::Object...) = unique(member for obj in x for member in obj.members)
members(x::Array{Object,1}) = unique(member for obj in x for member in obj.members)

groups(x::Object...) = unique(group for obj in x for group in obj.groups)
groups(x::Array{Object,1}) = unique(group for obj in x for group in obj.groups)

"""
(<m>::TimeSliceMap)(t::TimeSlice...)
Expand Down
12 changes: 4 additions & 8 deletions src/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ Base.intersect(s::T, ::Anything) where T<:AbstractArray = s

Base.in(item, ::Anything) = true

Base.iterate(o::Object) = iterate((o,))
Base.iterate(o::Object, state::T) where T = iterate((o,), state)
Base.iterate(t::TimeSlice) = iterate((t,))
Base.iterate(t::TimeSlice, state::T) where T = iterate((t,), state)
Base.iterate(o::Union{Object,TimeSlice}) = iterate((o,))
Base.iterate(o::Union{Object,TimeSlice}, state::T) where T = iterate((o,), state)
Base.iterate(v::ScalarParameterValue) = iterate((v,))
Base.iterate(v::ScalarParameterValue, state::T) where T = iterate((v,), state)

Base.length(t::TimeSlice) = 1
Base.length(o::Object) = 1
Base.length(t::Union{Object,TimeSlice}) = 1
Base.length(v::ScalarParameterValue) = 1

Base.isless(o1::Object, o2::Object) = o1.name < o2.name
Expand All @@ -41,8 +38,7 @@ Base.:(==)(o1::Object, o2::Object) = o1.id == o2.id
Base.:(==)(a::TimeSlice, b::TimeSlice) = a.id == b.id

Base.hash(::Anything) = zero(UInt64)
Base.hash(o::Object) = o.id
Base.hash(t::TimeSlice) = t.id
Base.hash(o::Union{Object,TimeSlice}) = o.id
Base.hash(r::RelationshipLike{K}) where {K} = hash(values(r))

Base.show(io::IO, ::Anything) = print(io, "anything")
Expand Down
3 changes: 1 addition & 2 deletions src/constructors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#############################################################################

Object(name::Symbol, id) = Object(name, id, [], [])
Object(name::AbstractString, args...) = Object(Symbol(name), args...)
Object(name::Symbol) = Base.invokelatest(Object, name) # NOTE: this allows us to override `Object` in `using_spinedb`

ObjectGroup(obj::Object) = ObjectGroup(obj.name, obj.id, [])

ObjectClass(name, objects, vals) = ObjectClass(name, objects, vals, Dict())
ObjectClass(name, objects) = ObjectClass(name, objects, Dict())

Expand Down
55 changes: 25 additions & 30 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#############################################################################

"""
AbstractObject
Supertype for [`Object`](@ref) and [`TimeSlice`](@ref).
"""
abstract type AbstractObject end

"""
AbstractParameterValue
Expand All @@ -47,12 +40,32 @@ struct Anything end
A type for representing an object in a Spine db.
"""
struct Object <: AbstractObject
struct Object
name::Symbol
id::UInt64
members::Array{Object,1}
groups::Array{Object,1}
end

"""
TimeSlice
A type for representing a slice of time.
"""
struct TimeSlice
start::Ref{DateTime}
end_::Ref{DateTime}
duration::Float64
blocks::NTuple{N,Object} where N
id::UInt64
function TimeSlice(start, end_, duration, blocks)
start > end_ && error("out of order")
id = objectid((start, end_, duration, blocks))
new(Ref(start), Ref(end_), duration, blocks, id)
end
end

ObjectLike = Union{T,Int64} where T<:AbstractObject
ObjectLike = Union{Object,TimeSlice,Int64}

RelationshipLike{K} = NamedTuple{K,V} where {K,V<:Tuple{Vararg{ObjectLike}}}

Expand All @@ -64,15 +77,15 @@ end
struct ObjectClass
name::Symbol
objects::Array{ObjectLike,1}
parameter_values::Dict{Object,Dict{Symbol,AbstractParameterValue}}
parameter_values::Dict{ObjectLike,Dict{Symbol,AbstractParameterValue}}
parameter_defaults::Dict{Symbol,AbstractParameterValue}
end

struct RelationshipClass
name::Symbol
object_class_names::Array{Symbol,1}
relationships::Array{RelationshipLike,1}
parameter_values::Dict{Tuple{Vararg{Object}},Dict{Symbol,AbstractParameterValue}}
parameter_values::Dict{Tuple{Vararg{ObjectLike}},Dict{Symbol,AbstractParameterValue}}
parameter_defaults::Dict{Symbol,AbstractParameterValue}
lookup_cache::Dict{Bool,Dict}
RelationshipClass(name, obj_cls_names, rels, vals, defaults) =
Expand All @@ -84,24 +97,6 @@ struct Parameter
classes::Array{Union{ObjectClass,RelationshipClass},1}
end

"""
TimeSlice
A type for representing a slice of time.
"""
struct TimeSlice <: AbstractObject
start::Ref{DateTime}
end_::Ref{DateTime}
duration::Float64
blocks::NTuple{N,Object} where N
id::UInt64
function TimeSlice(start, end_, duration, blocks)
start > end_ && error("out of order")
id = objectid((start, end_, duration, blocks))
new(Ref(start), Ref(end_), duration, blocks, id)
end
end

struct TimeSliceMap
time_slices::Array{TimeSlice,1}
index::Array{Int64,1}
Expand Down Expand Up @@ -205,7 +200,7 @@ struct RepeatingTimeSeriesParameterValue{V} <: AbstractTimeSeriesParameterValue
t_map::TimeSeriesMap
end

struct MapParameterValue{K,V} <: AbstractParameterValue where V<:AbstractParameterValue
struct MapParameterValue{K,V} <: AbstractParameterValue where V <: AbstractParameterValue
value::Map{K,V}
end

Expand Down
39 changes: 26 additions & 13 deletions src/using_spinedb.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,32 @@ function _members_per_group(groups)
end

"""
A Dict mapping `Int64` ids to the corresponding `Object` or `ObjectGroup`.
A Dict mapping member ids to an Array of entity group ids.
"""
function _full_objects_per_id(objects, members_per_group)
objects_per_id = Dict(obj["id"] => Object(obj["name"], obj["id"]) for obj in objects)
group_ids = intersect(keys(objects_per_id), keys(members_per_group))
# Promote `Object` to `ObjectGroup`
for group_id in group_ids
objects_per_id[group_id] = ObjectGroup(objects_per_id[group_id])
function _groups_per_member(groups)
d = Dict()
for grp in groups
push!(get!(d, grp["member_id"], []), grp["entity_id"])
end
d
end

"""
A Dict mapping `Int64` ids to the corresponding `Object`.
"""
function _full_objects_per_id(objects, members_per_group, groups_per_member)
objects_per_id = Dict{Int64,Object}(obj["id"] => Object(obj["name"], obj["id"]) for obj in objects)
# Specify `members` for each group
for (id, object) in objects_per_id
member_ids = get(members_per_group, id, ())
members = isempty(member_ids) ? [object] : [objects_per_id[member_id] for member_id in member_ids]
append!(object.members, members)
end
# Specify `members` for each `ObjectGroup`
for group_id in group_ids
obj_grp = objects_per_id[group_id]
members = [objects_per_id[member_id] for member_id in members_per_group[group_id]]
append!(obj_grp.members, members)
# Specify `groups` for each member
for (id, object) in objects_per_id
group_ids = get(groups_per_member, id, ())
groups = [objects_per_id[group_id] for group_id in group_ids]
append!(object.groups, groups)
end
objects_per_id
end
Expand Down Expand Up @@ -225,7 +237,8 @@ function using_spinedb(db_map::PyObject, mod=@__MODULE__)
param_defs = py"[x._asdict() for x in $db_map.query($db_map.parameter_definition_sq)]"
param_vals = py"[x._asdict() for x in $db_map.query($db_map.parameter_value_sq)]"
members_per_group = _members_per_group(groups)
full_objs_per_id = _full_objects_per_id(objects, members_per_group)
groups_per_member = _groups_per_member(groups)
full_objs_per_id = _full_objects_per_id(objects, members_per_group, groups_per_member)
objs_per_cls = _entities_per_class(objects)
rels_per_cls = _entities_per_class(relationships)
param_defs_per_cls = _parameter_definitions_per_class(param_defs)
Expand Down
16 changes: 12 additions & 4 deletions test/using_spinedb.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,27 @@
@testset "object_class" begin
object_classes = ["institution"]
institutions = ["VTT", "KTH", "KUL", "ER", "UCD"]
objects = [["institution", x] for x in institutions]
objects = [["institution", x] for x in (institutions..., "Spine")]
object_groups =[["institution", "Spine", [x for x in institutions]]]
db_api.create_new_spine_database(url)
db_api.import_data_to_url(url; object_classes=object_classes, objects=objects)
db_api.import_data_to_url(url; object_classes=object_classes, objects=objects, object_groups=object_groups)
using_spinedb(url)
@test length(institution()) === 5
@test length(institution()) === 6
@test all(x isa Object for x in institution())
@test [x.name for x in institution()] == Symbol.(institutions)
@test [x.name for x in institution()] == vcat(Symbol.(institutions), :Spine)
@test institution(:FIFA) === nothing
@test length(object_class()) === 1
@test all(x isa ObjectClass for x in object_class())
max_object_id = maximum(obj.id for obj in institution())
FIFA = Object(:FIFA)
@test FIFA.id === max_object_id + 1
Spine = institution(:Spine)
@test members(Spine) == institution()[1:end - 1]
@test isempty(groups(Spine))
@testset for i in institution()[1:end - 1]
@test members(i) == [i]
@test groups(i) == [Spine]
end
end
@testset "relationship_class" begin
object_classes = ["institution", "country"]
Expand Down

0 comments on commit 2c1ba45

Please sign in to comment.