Skip to content
Draft
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
6 changes: 6 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
RecursiveArrayToolsRaggedArrays = "13072b0f-2c55-5437-9ae7-d433b7a33950"

[sources]
RecursiveArrayTools = {path = ".."}
RecursiveArrayToolsRaggedArrays = {path = "../lib/RecursiveArrayToolsRaggedArrays"}

[compat]
Documenter = "1.3"
RecursiveArrayTools = "4"
RecursiveArrayToolsRaggedArrays = "1"
4 changes: 2 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Documenter, RecursiveArrayTools
using Documenter, RecursiveArrayTools, RecursiveArrayToolsRaggedArrays

cp("./docs/Manifest.toml", "./docs/src/assets/Manifest.toml", force = true)
cp("./docs/Project.toml", "./docs/src/assets/Project.toml", force = true)
Expand All @@ -8,7 +8,7 @@ include("pages.jl")
makedocs(
sitename = "RecursiveArrayTools.jl",
authors = "Chris Rackauckas",
modules = [RecursiveArrayTools],
modules = [RecursiveArrayTools, RecursiveArrayToolsRaggedArrays],
clean = true, doctest = false, linkcheck = true,
warnonly = [:missing_docs],
format = Documenter.HTML(
Expand Down
8 changes: 8 additions & 0 deletions docs/src/array_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ DiffEqArray
ArrayPartition
NamedArrayPartition
```

## Convenience Constructor Types

```@docs
VA
AP
AllObserved
```
8 changes: 7 additions & 1 deletion docs/src/assets/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
RecursiveArrayToolsRaggedArrays = "13072b0f-2c55-5437-9ae7-d433b7a33950"

[sources]
RecursiveArrayTools = {path = ".."}
RecursiveArrayToolsRaggedArrays = {path = "../lib/RecursiveArrayToolsRaggedArrays"}

[compat]
Documenter = "1.3"
RecursiveArrayTools = "3"
RecursiveArrayTools = "4"
RecursiveArrayToolsRaggedArrays = "1"
13 changes: 13 additions & 0 deletions docs/src/plotting.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,16 @@ additional features:

These advanced features are defined in SciMLBase and activate automatically
when plotting solution objects.

## Plot Helper API

```@docs
DEFAULT_PLOT_FUNC
plottable_indices
plot_indices
getindepsym_defaultt
interpret_vars
add_labels!
diffeq_to_arrays
solplot_vecs_and_labels
```
2 changes: 2 additions & 0 deletions docs/src/ragged_arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,6 @@ structure and access elements without implicit zeros.
```@docs
RecursiveArrayTools.AbstractRaggedVectorOfArray
RecursiveArrayTools.AbstractRaggedDiffEqArray
RecursiveArrayToolsRaggedArrays.RaggedVectorOfArray
RecursiveArrayToolsRaggedArrays.RaggedDiffEqArray
```
9 changes: 9 additions & 0 deletions docs/src/recursive_array_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and do not require that the RecursiveArrayTools types are used.
recursivecopy
recursivecopy!
recursivecopyto!
recursivefill!
vecvecapply
copyat_or_push!
vecvec_to_mat
recursive_one
recursive_mean
recursive_bottom_eltype
recursive_unitless_bottom_eltype
recursive_unitless_eltype
vecarr_to_vectors
tuples
```
19 changes: 18 additions & 1 deletion src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,12 @@ for type in [AbstractArray, AbstractVectorOfArray]
end
end

# Deprecated
"""
vecvec_to_mat(vecvec)

Convert a vector of equal-length vectors into a dense matrix whose rows are the
input vectors.
"""
function vecvec_to_mat(vecvec)
mat = Matrix{eltype(eltype(vecvec))}(undef, length(vecvec), length(vecvec[1]))
for i in 1:length(vecvec)
Expand Down Expand Up @@ -351,6 +356,12 @@ Calls `one` on the bottom container to get the "true element one type".
recursive_one(a) = recursive_one(a[1])
recursive_one(a::T) where {T <: Number} = one(a)

"""
recursive_bottom_eltype(x)

Return the scalar element type at the bottom of nested array-like element
types.
"""
recursive_bottom_eltype(a) = a == eltype(a) ? a : recursive_bottom_eltype(eltype(a))

"""
Expand Down Expand Up @@ -395,6 +406,12 @@ end
recursive_unitless_eltype(a::Type{T}) where {T <: Number} = typeof(one(eltype(a)))
recursive_unitless_eltype(::Type{<:Enum{T}}) where {T} = T

"""
recursive_mean(x)

Compute a mean value through recursive array containers without requiring
`Statistics.mean`.
"""
function recursive_mean(vecvec::Vector{T}) where {T <: AbstractArray}
out = zero(vecvec[1])
for i in eachindex(vecvec)
Expand Down
48 changes: 47 additions & 1 deletion src/vector_of_array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ mutable struct DiffEqArray{
dense::Bool
end
### Abstract Interface

"""
AllObserved()

Sentinel used by symbolic indexing paths to request all observed variables from
an `AbstractDiffEqArray`.
"""
struct AllObserved
end

Expand Down Expand Up @@ -936,6 +943,11 @@ function Base.:(==)(A::AbstractVectorOfArray, B::AbstractVectorOfArray)
end
# Comparison with plain arrays uses AbstractArray element-wise comparison via default

"""
tuples(A::DiffEqArray)

Return the saved time/state pairs of `A` as `(t, u)` tuples.
"""
tuples(VA::DiffEqArray) = tuple.(VA.t, VA.u)

# Growing the array simply adds to the container vector
Expand Down Expand Up @@ -1132,7 +1144,11 @@ function Base.fill!(VA::AbstractVectorOfArray, x)
end
return VA
end
# conversion tools
"""
vecarr_to_vectors(A::AbstractVectorOfArray)

Collect the component time series of `A` as one vector per component.
"""
vecarr_to_vectors(VA::AbstractVectorOfArray) = [VA[i, :] for i in eachindex(VA.u[1])]
# linear algebra
ArrayInterface.issingular(va::AbstractVectorOfArray) = ArrayInterface.issingular(Matrix(va))
Expand Down Expand Up @@ -1170,11 +1186,30 @@ end

## Plotting helper functions (shared with SciMLBase)

"""
DEFAULT_PLOT_FUNC(x, y)
DEFAULT_PLOT_FUNC(x, y, z)

Default transformation used by plotting helpers when no custom plotting
function is supplied.
"""
DEFAULT_PLOT_FUNC(x, y) = (x, y)
DEFAULT_PLOT_FUNC(x, y, z) = (x, y, z)

"""
plottable_indices(x)

Return the default component indices to plot for `x`.
"""
plottable_indices(x::AbstractArray) = 1:length(x)
plottable_indices(x::Number) = 1

"""
plot_indices(A::AbstractArray)

Return the index iterator used when expanding array-valued data into plot
series.
"""
plot_indices(A::AbstractArray) = eachindex(A)

"""
Expand Down Expand Up @@ -1266,6 +1301,11 @@ function _var_label(A, x)
end
end

"""
add_labels!(labels, x, dims, A, strs)

Append the plot label for the variable tuple `x` to `labels`.
"""
function add_labels!(labels, x, dims, A, strs)
if ((x[2] isa Integer && x[2] == 0) || isequal(x[2], getindepsym_defaultt(A))) &&
dims == 2
Expand Down Expand Up @@ -1319,6 +1359,12 @@ function diffeq_to_arrays(
return solplot_vecs_and_labels(dims, vars, plott, A)
end

"""
solplot_vecs_and_labels(dims, vars, plott, A)

Build plot vectors and labels for interpreted plotting variables over the
sample points `plott`.
"""
function solplot_vecs_and_labels(dims, vars, plott, A)
plot_vecs = []
labels = String[]
Expand Down
163 changes: 163 additions & 0 deletions test/QA/qa.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,168 @@
using SciMLTesting, RecursiveArrayTools, Test, Pkg

const PACKAGE_ROOT = normpath(joinpath(@__DIR__, "..", ".."))

function source_roots()
roots = [joinpath(PACKAGE_ROOT, "src")]
lib_root = joinpath(PACKAGE_ROOT, "lib")
if isdir(lib_root)
append!(roots, joinpath.(lib_root, readdir(lib_root), "src"))
end
return filter(isdir, roots)
end

function source_files()
files = String[]
for root in source_roots()
for (dir, _, filenames) in walkdir(root), filename in filenames
endswith(filename, ".jl") && push!(files, joinpath(dir, filename))
end
end
return sort(files)
end

count_triple_quotes(line) = length(collect(eachmatch(r"\"\"\"", line)))
strip_line_comment(line) = replace(line, r"#.*" => "")

function public_names_from_statement(statement)
statement = replace(statement, r"^\s*(export|public|@public)\s+" => "")
statement = replace(statement, r"^\s*SciMLPublic\.@public\s+" => "")
ignored = Set(["Expr", "SciMLPublic", "eval", "export", "public", "return"])
names = Symbol[]
for match in eachmatch(r"[A-Za-z_][A-Za-z0-9_!]*", statement)
name = match.match
name in ignored && continue
push!(names, Symbol(name))
end
return names
end

function public_api_names()
names = Set{Symbol}()
for file in source_files()
lines = readlines(file)
in_docstring = false
index = 1
while index <= length(lines)
line = lines[index]
raw = in_docstring ? "" : strip_line_comment(line)
if occursin(r"^\s*(export|public|@public)\b", raw) ||
occursin(r"^\s*SciMLPublic\.", raw)
statement = raw
parens = count(==('('), statement) - count(==(')'), statement)
while (endswith(rstrip(statement), ",") || parens > 0) &&
index < length(lines)
index += 1
statement *= " " * strip_line_comment(lines[index])
parens = count(==('('), statement) - count(==(')'), statement)
end
union!(names, public_names_from_statement(statement))
end
isodd(count_triple_quotes(line)) && (in_docstring = !in_docstring)
index += 1
end
end
return names
end

is_doc_macro(x) = x == Symbol("@doc") ||
(x isa GlobalRef && x.name == Symbol("@doc"))

function definition_head_name(x)
x isa Symbol && return x
x isa Expr || return nothing
if x.head in (:curly, :where, :call, :<:)
return definition_head_name(x.args[1])
elseif x.head == :.
last = x.args[end]
return last isa QuoteNode ? last.value : last
else
return nothing
end
end

function documented_definition_names(x)
names = Set{Symbol}()
x isa Expr || return names
name = if x.head == :struct
definition_head_name(x.args[2])
elseif x.head == :abstract
definition_head_name(x.args[1])
elseif x.head in (:function, :(=))
definition_head_name(x.args[1])
elseif x.head == :const
return documented_definition_names(x.args[1])
else
nothing
end
name isa Symbol && push!(names, name)
return names
end

function collect_docstring_names!(names, x)
x isa Expr || return names
if x.head == :macrocall && length(x.args) >= 4 && is_doc_macro(x.args[1])
union!(names, documented_definition_names(x.args[end]))
end
foreach(arg -> collect_docstring_names!(names, arg), x.args)
return names
end

function source_docstring_names()
names = Set{Symbol}()
for file in source_files()
collect_docstring_names!(names, Meta.parseall(read(file, String)))
end
return names
end

function rendered_docs_pages()
docs_root = joinpath(PACKAGE_ROOT, "docs")
pages_file = joinpath(docs_root, "pages.jl")
pages = String[]
for match in eachmatch(r"\"([^\"]+\.md)\"", read(pages_file, String))
path = joinpath(docs_root, "src", match.captures[1])
isfile(path) && push!(pages, path)
end
return unique(pages)
end

function docs_entry_name(line)
item = split(strip_line_comment(line))[1]
component = split(item, '.')[end]
match = Base.match(r"^([A-Za-z_][A-Za-z0-9_!]*)", component)
return match === nothing ? nothing : Symbol(match.captures[1])
end

function rendered_docs_entries()
entries = Set{Symbol}()
for file in rendered_docs_pages()
in_docs_block = false
for line in eachline(file)
stripped = strip(line)
if startswith(stripped, "```@docs")
in_docs_block = true
elseif in_docs_block && startswith(stripped, "```")
in_docs_block = false
elseif in_docs_block && !isempty(stripped) && !startswith(stripped, "#")
name = docs_entry_name(stripped)
name isa Symbol && push!(entries, name)
end
end
end
return entries
end

@testset "Public API documentation coverage" begin
public_api = public_api_names()

missing_source_docstrings = sort!(collect(setdiff(public_api, source_docstring_names())))
@test isempty(missing_source_docstrings)

missing_rendered_docs = sort!(collect(setdiff(public_api, rendered_docs_entries())))
@test isempty(missing_rendered_docs)
end

# yes this is horrible, we'll fix it when Pkg or Base provides a decent API
manifest = Pkg.Types.EnvCache().manifest
# these are good sentinels to test whether someone has added a heavy SciML package to the test deps
Expand Down
Loading