Skip to content
Merged
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
10 changes: 8 additions & 2 deletions docs/src/api.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# API

```@autodocs
Modules = [SurrogatesBase]
```@docs
SurrogatesBase.AbstractDeterministicSurrogate
SurrogatesBase.AbstractStochasticSurrogate
SurrogatesBase.update!
SurrogatesBase.parameters
SurrogatesBase.update_hyperparameters!
SurrogatesBase.hyperparameters
SurrogatesBase.finite_posterior
```
220 changes: 189 additions & 31 deletions src/SurrogatesBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,63 @@ export finite_posterior

"""
abstract type AbstractDeterministicSurrogate <: Function end
(s::AbstractDeterministicSurrogate)(xs)

An abstract type for deterministic surrogates.
Interface tag for deterministic surrogate models.

(s::AbstractDeterministicSurrogate)(xs)
Subtypes approximate a deterministic function, or a deterministic statistic of a
conditional distribution, from observed data. A deterministic surrogate is callable on a
collection of input points `xs` and should return one surrogate value for each point.

# Required Methods

- `(s)(xs)`: evaluate the surrogate at the points in `xs`.
- [`update!(s, new_xs, new_ys)`](@ref): incorporate new observations.

# Optional Methods

Subtypes of `AbstractDeterministicSurrogate` are callable with a `Vector` of points `xs`.
The result is a `Vector` of evaluations of the surrogate at points `xs`, corresponding to
approximations of the underlying function at points `xs` respectively.
- [`parameters(s)`](@ref): return learned parameter values.
- [`hyperparameters(s)`](@ref): return tunable hyperparameter values.
- [`update_hyperparameters!(s, prior)`](@ref): update tunable hyperparameters.

# Examples

```jldoctest
julia> struct ZeroSurrogate <: AbstractDeterministicSurrogate end
julia> struct ConstantSurrogate{T} <: AbstractDeterministicSurrogate
value::T
end

julia> (::ZeroSurrogate)(xs) = 0
julia> (s::ConstantSurrogate)(xs) = fill(s.value, length(xs));

julia> s = ZeroSurrogate();
julia> surrogate = ConstantSurrogate(1.5);

julia> s([4]) == 0
true
julia> surrogate([[0.0, 1.0], [1.0, 2.0]])
2-element Vector{Float64}:
1.5
1.5
```
"""
abstract type AbstractDeterministicSurrogate <: Function end

"""
abstract type AbstractStochasticSurrogate end

An abstract type for stochastic surrogates.
Interface tag for stochastic surrogate models.

Subtypes approximate a conditional distribution, stochastic process, or uncertainty-aware
surrogate from observed data.

# Required Methods

- [`update!(s, new_xs, new_ys)`](@ref): incorporate new observations.
- [`finite_posterior(s, xs)`](@ref): return a finite-dimensional posterior object at
the points in `xs`.

# Optional Methods

- [`parameters(s)`](@ref): return learned parameter values.
- [`hyperparameters(s)`](@ref): return tunable hyperparameter values.
- [`update_hyperparameters!(s, prior)`](@ref): update tunable hyperparameters.

See also [`finite_posterior`](@ref).
"""
Expand All @@ -45,30 +74,113 @@ abstract type AbstractStochasticSurrogate end
"""
update!(s, new_xs::AbstractVector, new_ys::AbstractVector)

Include data `new_ys` at points `new_xs` into the surrogate `s`, i.e., refit the surrogate `s`
to incorporate new data points.
Incorporate observations `new_ys` at points `new_xs` into the surrogate `s`.

Implementations usually mutate and return `s`. For deterministic surrogates, `new_ys`
contains function evaluations or deterministic statistics. For stochastic surrogates,
`new_ys` contains observed samples from the modeled conditional distribution.

# Arguments

- `s`: surrogate to refit or update.
- `new_xs`: input points to add to `s`.
- `new_ys`: observed values corresponding to `new_xs`.

Use `update!(s, eachslice(X; dims = 2), new_ys)` when columns of a matrix `X` are the input
points.

# Examples

```jldoctest
julia> mutable struct UpdateExampleSurrogate <: AbstractDeterministicSurrogate
xs::Vector{Float64}
ys::Vector{Float64}
end

julia> (s::UpdateExampleSurrogate)(xs) = fill(last(s.ys), length(xs));

If the surrogate `s` is a deterministic surrogate, the `new_ys` correspond to function
evaluations, if `s` is a stochastic surrogate, the `new_ys` are samples from a conditional
probability distribution.
julia> function SurrogatesBase.update!(s::UpdateExampleSurrogate, new_xs, new_ys)
append!(s.xs, new_xs)
append!(s.ys, new_ys)
return s
end;

Use `update!(s, eachslice(X, dims = 2), new_ys)` if `X` is a matrix.
julia> surrogate = UpdateExampleSurrogate(Float64[], Float64[]);

julia> update!(surrogate, [1.0, 2.0], [3.0, 4.0]) === surrogate
true

julia> surrogate.ys
2-element Vector{Float64}:
3.0
4.0
```
"""
function update! end

"""
parameters(s)

Returns current values of parameters used in surrogate `s`.
Return the current learned parameter values of the surrogate `s`.

This is an optional interface method for surrogate implementations that expose fitted
parameters separately from tunable hyperparameters.

# Examples

```jldoctest
julia> struct ParameterExampleSurrogate <: AbstractDeterministicSurrogate
weights::Vector{Float64}
end

julia> (s::ParameterExampleSurrogate)(xs) = fill(sum(s.weights), length(xs));

julia> SurrogatesBase.parameters(s::ParameterExampleSurrogate) = s.weights;

julia> parameters(ParameterExampleSurrogate([1.0, 2.0]))
2-element Vector{Float64}:
1.0
2.0
```
"""
function parameters end

"""
update_hyperparameters!(s, prior)

Update the hyperparameters of the surrogate `s` by performing hyperparameter optimization
using the information in `prior`. After changing hyperparameters of `s`, fit `s` to past
data.
Update tunable hyperparameters of the surrogate `s` using information in `prior`.

Implementations usually mutate and return `s`. After changing hyperparameters, the
surrogate should be refit to its existing observations when the hyperparameters affect the
fitted representation.

# Arguments

- `s`: surrogate whose hyperparameters are updated.
- `prior`: implementation-defined prior, bounds, or configuration used by the update.

# Examples

```jldoctest
julia> mutable struct HyperparameterUpdateExample <: AbstractDeterministicSurrogate
scale::Float64
end

julia> (s::HyperparameterUpdateExample)(xs) = fill(s.scale, length(xs));

julia> function SurrogatesBase.update_hyperparameters!(s::HyperparameterUpdateExample, prior)
s.scale = (s.scale + prior.scale) / 2
return s
end;

julia> surrogate = HyperparameterUpdateExample(2.0);

julia> update_hyperparameters!(surrogate, (; scale = 4.0)) === surrogate
true

julia> surrogate.scale
3.0
```

See also [`hyperparameters`](@ref).
"""
Expand All @@ -77,7 +189,25 @@ function update_hyperparameters! end
"""
hyperparameters(s)

Returns current values of hyperparameters.
Return the current tunable hyperparameter values of the surrogate `s`.

This is an optional interface method for surrogate implementations with configuration
values that control fitting or posterior construction.

# Examples

```jldoctest
julia> struct HyperparameterReadExample <: AbstractDeterministicSurrogate
settings::NamedTuple
end

julia> (s::HyperparameterReadExample)(xs) = fill(s.settings.scale, length(xs));

julia> SurrogatesBase.hyperparameters(s::HyperparameterReadExample) = s.settings;

julia> hyperparameters(HyperparameterReadExample((; scale = 2.0)))
(scale = 2.0,)
```

See also [`update_hyperparameters!`](@ref).
"""
Expand All @@ -86,18 +216,46 @@ function hyperparameters end
"""
finite_posterior(s::AbstractStochasticSurrogate, xs::AbstractVector)

Return a posterior distribution at points `xs`.
Return a finite-dimensional posterior object at points `xs`.

The returned object represents the joint posterior over the requested points. An
`AbstractStochasticSurrogate` implementation may support some or all of the following
methods on that object:

- `mean(finite_posterior(s, xs))`: posterior means at `xs`.
- `var(finite_posterior(s, xs))`: posterior variances at `xs`.
- `mean_and_var(finite_posterior(s, xs))`: posterior means and variances at `xs`.
- `rand(finite_posterior(s, xs))`: a sample from the joint posterior at `xs`.

Use `mean(finite_posterior(s, eachslice(X; dims = 2)))` when columns of a matrix `X` are
the input points.

An `AbstractStochasticSurrogate` might implement some or all of the following methods on
the returned object:
# Examples

```jldoctest
julia> using Statistics

julia> struct PosteriorExampleSurrogate <: AbstractStochasticSurrogate
value::Float64
end

- `mean(finite_posterior(s,xs))` returns a `Vector` of posterior means at `xs`
- `var(finite_posterior(s,xs))` returns a `Vector` of posterior variances at `xs`
- `mean_and_var(finite_posterior(s,xs))` returns a `Tuple` consisting of a `Vector` of posterior means and a `Vector` of posterior variances at `xs`
- `rand(finite_posterior(s,xs))` returns a `Vector`, which is a sample from the joint
posterior at points `xs`
julia> struct PosteriorExample
means::Vector{Float64}
end

Use `mean(finite_posterior(s, eachslice(X, dims = 2)))` if `X` is a matrix.
julia> Statistics.mean(p::PosteriorExample) = p.means;

julia> function SurrogatesBase.finite_posterior(s::PosteriorExampleSurrogate, xs)
return PosteriorExample(fill(s.value, length(xs)))
end;

julia> posterior = finite_posterior(PosteriorExampleSurrogate(1.25), [0.0, 1.0]);

julia> mean(posterior)
2-element Vector{Float64}:
1.25
1.25
```
"""
function finite_posterior end

Expand Down
39 changes: 39 additions & 0 deletions test/qa/qa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,45 @@ import Statistics

run_qa(SurrogatesBase; explicit_imports = true)

function documented_api_names()
api_path = joinpath(@__DIR__, "..", "..", "docs", "src", "api.md")
prefix = string(nameof(SurrogatesBase), ".")
documented_names = Symbol[]

for line in eachline(api_path)
stripped = strip(line)
startswith(stripped, prefix) || continue
push!(documented_names, Symbol(stripped[(lastindex(prefix) + 1):end]))
end

return sort!(unique(documented_names))
end

function has_source_docstring(mod::Module, name::Symbol)
doc = Docs.doc(getfield(mod, name))
doc === nothing && return false

return !occursin("No documentation found", sprint(show, MIME("text/plain"), doc))
end

@testset "public API documentation coverage" begin
public_names = sort!(
setdiff(
names(SurrogatesBase; all = false, imported = false),
[nameof(SurrogatesBase)]
)
)

missing_docstrings = [
name for name in public_names
if !has_source_docstring(SurrogatesBase, name)
]
missing_docs_entries = setdiff(public_names, documented_api_names())

@test missing_docstrings == Symbol[]
@test missing_docs_entries == Symbol[]
end

# JET.report_call type-stability analysis of concrete user-defined surrogates.
# This goes beyond run_qa's package-level JET.test_package: it checks that the
# interface contract (update!/finite_posterior/parameters and the call method)
Expand Down
Loading