Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/IMASdd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import PrecompileTools
import OrderedCollections
import CoordinateConventions

const globals_threads_lock = ReentrantLock()

const document = OrderedCollections.OrderedDict()

include("data_header.jl")
Expand Down
124 changes: 47 additions & 77 deletions src/data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct Coordinates{T}
end

"""
coordinates(@nospecialize(ids::IDS), field::Symbol; coord_leaves::Union{Nothing,Vector{<:Union{Nothing,Symbol}}}=nothing, to_cocos::Int=internal_cocos)
coordinates(ids::IDS, field::Symbol; coord_leaves::Union{Nothing,Vector{<:Union{Nothing,Symbol}}}=nothing, to_cocos::Int=internal_cocos)

Return two lists, one of coordinate names and the other with their values in the data structure

Expand All @@ -85,7 +85,7 @@ Coordinate value is `missing` if the coordinate is missing in the data structure

Use `coord_leaves` to override fetching coordinates of a given field
"""
function coordinates(@nospecialize(ids::IDS), field::Symbol; coord_leaves::Union{Nothing,Vector{<:Union{Nothing,Symbol}}}=nothing)
function coordinates(ids::IDS, field::Symbol; coord_leaves::Union{Nothing,Vector{<:Union{Nothing,Symbol}}}=nothing)
coord_names = String[coord for coord in info(ids, field).coordinates]
coord_fills = Vector{Bool}(undef, length(coord_names))

Expand Down Expand Up @@ -248,11 +248,11 @@ function concrete_array_type(T)
end

"""
getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int=user_cocos)
getproperty(ids::IDS, field::Symbol; to_cocos::Int=user_cocos)

Return IDS value for requested field
"""
function Base.getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int=user_cocos)
function Base.getproperty(ids::IDS, field::Symbol; to_cocos::Int=user_cocos)
# @assert isempty(cocos_transform(ids, field))
value = _getproperty(ids, field; to_cocos)
if typeof(value) <: Exception
Expand All @@ -262,13 +262,13 @@ function Base.getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int=
end

"""
getproperty(@nospecialize(ids::IDS), field::Symbol, @nospecialize(default::Any); to_cocos::Int=user_cocos)
getproperty(ids::IDS, field::Symbol, @nospecialize(default::Any); to_cocos::Int=user_cocos)

Return IDS value for requested field or `default` if field is missing

NOTE: This is useful because accessing a `missing` field in an IDS would raise an error
"""
function Base.getproperty(@nospecialize(ids::IDS), field::Symbol, @nospecialize(default::Any); to_cocos::Int=user_cocos)
function Base.getproperty(ids::IDS, field::Symbol, default::Any; to_cocos::Int=user_cocos)
# @assert isempty(cocos_transform(ids, field))
value = _getproperty(ids, field; to_cocos)
if typeof(value) <: Exception
Expand All @@ -282,14 +282,14 @@ export getproperty
push!(document[:Base], :getproperty)

"""
getraw(@nospecialize(ids::IDS), field::Symbol)
getraw(ids::IDS, field::Symbol)

Returns data, expression function, or missing

- Does not raise an error on missing data, returns missing
- Does not evaluate expressions
"""
function getraw(@nospecialize(ids::IDS), field::Symbol)
function getraw(ids::IDS, field::Symbol)
@assert field ∉ private_fields error("Use `getfield(ids, :$field)` instead of getraw(ids, :$field)")

value = getfield(ids, field)
Expand All @@ -298,10 +298,6 @@ function getraw(@nospecialize(ids::IDS), field::Symbol)
# nothing to do for data structures
return value

elseif field == :global_time
# global time
return value

elseif hasdata(ids, field)
# has data
return value
Expand Down Expand Up @@ -356,7 +352,7 @@ Returns true if the ids field has no data (or expression)

NOTE: By default it does not include nor evaluate expressions
"""
function Base.isempty(@nospecialize(ids::IDS), field::Symbol; include_expr::Bool=false, eval_expr::Bool=false)
function Base.isempty(ids::IDS, field::Symbol; include_expr::Bool=false, eval_expr::Bool=false)
value = getfield(ids, field)
if typeof(value) <: IDSvector # filled arrays of structures
return isempty(value)
Expand All @@ -379,14 +375,14 @@ push!(document[:Base], :isempty)

Returns if the ids has been frozen
"""
function isfrozen(@nospecialize(ids::IDS))
@inline function isfrozen(@nospecialize(ids::IDS))
return getfield(ids, :_frozen)
end

export isfrozen
push!(document[:Base], :isfrozen)

function _getproperty(@nospecialize(ids::IDSraw), field::Symbol; to_cocos::Int)
Base.@constprop :aggressive function _getproperty(ids::IDSraw, field::Symbol; to_cocos::Int)
if field ∈ private_fields
error("Use `getfield(ids, :$field)` instead of `ids.$field`")
end
Expand All @@ -404,8 +400,10 @@ function _getproperty(@nospecialize(ids::IDSraw), field::Symbol; to_cocos::Int)
return IMASmissingDataException(ids, field)
end

function _getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int)
if field ∈ private_fields
Base.@constprop :aggressive function _getproperty(ids::IDS, field::Symbol; to_cocos::Int)
if field === :global_time
return global_time(ids)
elseif field ∈ private_fields
error("Use `getfield(ids, :$field)` instead of `ids.$field`")
elseif !hasfield(typeof(ids), field)
error("type $(typeof(ids)) has no field `$(field)`\nDid you mean:\n * $(join(keys(ids),"\n * "))")
Expand All @@ -418,10 +416,6 @@ function _getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int)
# nothing to do for data structures
return value

elseif field === :global_time
# nothing to do for global_time
return value

elseif hasdata(ids, field)
# has data
valid = true
Expand All @@ -444,7 +438,6 @@ function _getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int)
if onetime # onetime_expression
#println("onetime_expression: $(location(ids, field))")
setproperty!(ids, field, value; error_on_missing_coordinates=false)
expression_onetime_weakref[objectid(ids)] = WeakRef(ids)
end
valid = true
break
Expand All @@ -471,21 +464,24 @@ function _getproperty(@nospecialize(ids::IDS), field::Symbol; to_cocos::Int)
end
end

function _setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Union{AbstractRange,StaticArraysCore.SVector,StaticArraysCore.MVector,SubArray}; from_cocos::Int)
Base.@constprop :aggressive function _setproperty!(ids::IDS, field::Symbol, value::Union{AbstractRange,StaticArraysCore.SVector,StaticArraysCore.MVector,SubArray}; from_cocos::Int)
return _setproperty!(ids, field, collect(value); from_cocos)
end

"""
_setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Any)
_setproperty!(ids::IDS, field::Symbol, value::Any)

Like setfield! but also add to list of filled fields
"""
function _setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Any; from_cocos::Int)
T = eltype(ids)
if field in private_fields
Base.@constprop :aggressive function _setproperty!(ids::IDS, field::Symbol, value::Any; from_cocos::Int)
if field == :global_time
return global_time(ids, value)
elseif field in private_fields
error("Use `setfield!(ids, :$field, ...)` instead of _setproperty!(ids, :$field ...)")
end

T = eltype(ids)

# nice error if type is wrong
tp = fieldtype_typeof(ids, field)
if !(typeof(value) <: tp)
Expand All @@ -505,7 +501,7 @@ function _setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Any; from_
setfield!(value, :_parent, WeakRef(ids))
end

# may need cocos conversion
# may need cocos conversion
if (from_cocos != internal_cocos) && (eltype(value) <: Real)
cocos_multiplier = transform_cocos_coming_in(ids, field, from_cocos)
if cocos_multiplier != 1.0
Expand All @@ -532,10 +528,8 @@ end

Utility function to set the _filled field of an IDS and the upstream parents
"""
function add_filled(@nospecialize(ids::IDS), field::Symbol)
if field !== :global_time
push!(getfield(ids, :_filled), field)
end
@inline function add_filled(@nospecialize(ids::IDS), field::Symbol)
push!(getfield(ids, :_filled), field)
return add_filled(ids)
end

Expand Down Expand Up @@ -584,16 +578,16 @@ end
"""
Base.setproperty!(ids::IDS, field::Symbol, value; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true)
"""
function Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Any; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
function Base.setproperty!(ids::IDS, field::Symbol, value::Any; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
return _setproperty!(ids, field, value; from_cocos)
end

"""
Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::AbstractArray{<:IDS}; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true)
Base.setproperty!(ids::IDS, field::Symbol, value::AbstractArray{<:IDS}; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true)

Handle setproperty of entire vectors of IDS structures at once (ids.field is of type IDSvector)
"""
function Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::AbstractArray{<:IDS}; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
function Base.setproperty!(ids::IDS, field::Symbol, value::AbstractArray{<:IDS}; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
orig = getfield(ids, field)
empty!(orig)
append!(orig, value)
Expand All @@ -602,13 +596,13 @@ function Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Abstra
end

"""
Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::AbstractArray; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true)
Base.setproperty!(ids::IDS, field::Symbol, value::AbstractArray; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true)

Ensures coordinates are set before the data that depends on those coordinates.

If `skip_non_coordinates` is set, then fields that are not coordinates will be silently skipped.
"""
function Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::AbstractArray; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
function Base.setproperty!(ids::IDS, field::Symbol, value::AbstractArray; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
if field ∉ getfield(ids, :_filled) && error_on_missing_coordinates
# figure out the coordinates
coords = coordinates(ids, field)
Expand All @@ -626,7 +620,7 @@ function Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::Abstra
return _setproperty!(ids, field, value; from_cocos)
end

function Base.setproperty!(@nospecialize(ids::IDS), field::Symbol, value::AbstractDict; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
function Base.setproperty!(ids::IDS, field::Symbol, value::AbstractDict; skip_non_coordinates::Bool=false, error_on_missing_coordinates::Bool=true, from_cocos::Int=user_cocos)
return _setproperty!(ids, field, string(value); from_cocos)
end

Expand Down Expand Up @@ -766,6 +760,15 @@ function Base.pop!(@nospecialize(ids::IDSvector{T})) where {T<:IDSvectorElement}
return tmp
end

function Base.popfirst!(@nospecialize(ids::IDSvector{T})) where {T<:IDSvectorElement}
tmp = popfirst!(ids._value)
if isempty(ids)
del_filled(ids)
end
return tmp
end


"""
merge!(@nospecialize(target_ids::T), @nospecialize(source_ids::T)) where {T<:IDS}
"""
Expand Down Expand Up @@ -845,7 +848,7 @@ Returns generator of fields in a IDS whether they are filled with data or not
"""
function Base.keys(@nospecialize(ids::IDS))
ns = NoSpecialize(ids)
return (field for field in fieldnames(typeof(ns.ids)) if field ∉ private_fields && field !== :global_time)
return (field for field in fieldnames(typeof(ns.ids)) if field ∉ private_fields)
end

"""
Expand All @@ -854,7 +857,7 @@ end
Returns generator of fields with data in a IDS

NOTE: By default it includes expressions, but does not evaluate them.
It assumes that a IDStop without data will also have no valid expressions.
It assumes that a IDStop without data will also have no valid expressions.
"""
function keys_no_missing(@nospecialize(ids::IDS); include_expr::Bool=true, eval_expr::Bool=false)
ns = NoSpecialize(ids)
Expand Down Expand Up @@ -913,7 +916,7 @@ end
#= ====== =#
function Base.empty!(@nospecialize(ids::T)) where {T<:IDS}
tmp = typeof(ids)()
@assert isempty(thread_in_expression(ids))
@assert isempty(thread_in_expression(ids))
for item in fieldnames(typeof(ids))
if item === :_filled
empty!(getfield(ids, :_filled))
Expand Down Expand Up @@ -951,39 +954,6 @@ end
#= ======= =#
# resize! #
#= ======= =#
function Base.resize!(@nospecialize(ids::IDSvector{T}); wipe::Bool=true) where {T<:IDSvectorTimeElement}
time0 = global_time(ids)
return resize!(ids, time0; wipe)
end

function Base.resize!(@nospecialize(ids::IDSvector{T}), time0::Float64; wipe::Bool=true) where {T<:IDSvectorTimeElement}
if isempty(ids) || (time0 > ids[end].time)
k = length(ids) + 1
elseif time0 == ids[end].time
k = length(ids)
else
for k in eachindex(ids)
if time0 == ids[k].time
if wipe
empty!(ids[k])
end
return ids[k]
end
end
error("Cannot resize structure at time $time0 for a time array structure already ranging between $(ids[1].time) and $(ids[end].time)")
end

resize!(ids, k; wipe)
ids[k].time = time0 # note IDSvectorTimeElement should always have a .time field

unifm_time = time_array_parent(ids)
if isempty(unifm_time) || time0 != unifm_time[end]
push!(unifm_time, time0)
end

return ids[k]
end

function Base.resize!(@nospecialize(ids::T), n::Int; wipe::Bool=true) where {T<:IDSvector{<:IDSvectorElement}}
if n > length(ids)
for k in length(ids):n-1
Expand Down Expand Up @@ -1036,7 +1006,7 @@ function Base.resize!(
return _set_conditions(match, conditions...)
elseif length(matches) > 1
if error_multiple_matches
error("Multiple entries $([k for k in keys(matches)]) match resize! conditions: $conditions")
error("Multiple entries $([k for k in keys(matches)]) of $(location(ids)) match resize!() conditions: $conditions")
else
for (kk, k) in reverse!(collect(enumerate(sort!(collect(keys(matches))))))
if kk == 1
Expand Down Expand Up @@ -1470,9 +1440,9 @@ function selective_copy!(@nospecialize(h_in::IDS), @nospecialize(h_out::IDS), pa
end
if typeof(h_out) <: IMASdd.dd
if time0 != NaN
h_out.global_time = time0
global_time(h_out, time0)
else
h_out.global_time = h_in.global_time
global_time(h_out, global_time(h_in))
end
end
return nothing
Expand Down
20 changes: 12 additions & 8 deletions src/data_header.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ abstract type IDSvectorTimeElement{T} <: IDSvectorElement{T} end
mutable struct IDSvector{T} <: AbstractVector{T}
_value::Vector{T}
_parent::WeakRef
function IDSvector(ids::Vector{T}) where {T<:IDSvectorElement}
return new{T}(ids, WeakRef(nothing))
end
_threads_lock::ReentrantLock
end

function IDSvector(ids::Vector{T}) where {T<:IDSvectorElement}
return IDSvector{T}(ids, WeakRef(nothing), ReentrantLock())
end

function IDSvector{T}() where {T}
return IDSvector(T[])
end

struct Info{T<:Tuple{Vararg{String}}}
Expand All @@ -36,10 +42,8 @@ struct Info{T<:Tuple{Vararg{String}}}
cocos_transform::Vector{String}
end

IDSvector{T}() where {T} = IDSvector(T[])

@inline function Base.eltype(@nospecialize(ids::IDS))
return typeof(ids).parameters[1]
@inline function Base.eltype(@nospecialize(ids::IDS{T})) where {T}
return T
end

const private_fields = (:_filled, :_frozen, :_threads_lock, :_in_expression, :_ref, :_parent, :_aux)
const private_fields = (:_filled, :_frozen, :_threads_lock, :_in_expression, :_ref, :_parent, :_aux, :_global_time)
Loading
Loading