diff --git a/Project.toml b/Project.toml
index 83bc082..9bcf798 100644
--- a/Project.toml
+++ b/Project.toml
@@ -5,19 +5,15 @@ version = "1.1.3"
[compat]
AllocCheck = "0.2"
-Aqua = "0.8"
-JET = "0.9, 0.10, 0.11"
LinearAlgebra = "1.10"
SafeTestsets = "0.1"
-SciMLTesting = "2.1"
+SciMLTesting = "2.4"
Statistics = "1.10"
Test = "1.10"
julia = "1.10"
[extras]
AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a"
-Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
-JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
@@ -25,4 +21,4 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
-test = ["AllocCheck", "Aqua", "JET", "LinearAlgebra", "SafeTestsets", "SciMLTesting", "Statistics", "Test"]
+test = ["AllocCheck", "LinearAlgebra", "SafeTestsets", "SciMLTesting", "Statistics", "Test"]
diff --git a/docs/make.jl b/docs/make.jl
index ac28b3b..e0b0016 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -6,13 +6,10 @@ DocMeta.setdocmeta!(
:(using SurrogatesBase)
)
-cp("./docs/Manifest.toml", "./docs/src/assets/Manifest.toml", force = true)
-cp("./docs/Project.toml", "./docs/src/assets/Project.toml", force = true)
-
pages = [
"Home" => "index.md",
- "interface.md",
- "api.md",
+ "Developer Interface" => "interface.md",
+ "Public API" => "api.md",
]
ENV["GKSwstype"] = "100"
@@ -21,6 +18,7 @@ makedocs(
modules = [SurrogatesBase],
sitename = "SurrogatesBase.jl",
clean = true,
+ checkdocs = :exports,
doctest = true,
linkcheck = true,
format = Documenter.HTML(
diff --git a/docs/src/api.md b/docs/src/api.md
index ccbe5c0..e2f0681 100644
--- a/docs/src/api.md
+++ b/docs/src/api.md
@@ -1,6 +1,7 @@
-# API
+# Public API
```@docs
+SurrogatesBase
SurrogatesBase.AbstractDeterministicSurrogate
SurrogatesBase.AbstractStochasticSurrogate
SurrogatesBase.update!
diff --git a/docs/src/index.md b/docs/src/index.md
index 6ef180c..2ff5a27 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -24,7 +24,7 @@ Pkg.add("SurrogatesBase")
## Contributing
- Please refer to the
- [SciML ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://github.com/SciML/ColPrac/blob/master/README.md)
+ [SciML ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://docs.sciml.ai/ColPrac/stable/)
for guidance on PRs, issues, and other matters relating to contributing to SciML.
- See the [SciML Style Guide](https://github.com/SciML/SciMLStyle) for common coding practices and other style decisions.
@@ -36,61 +36,3 @@ Pkg.add("SurrogatesBase")
[Julia Zulip](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged)
+ On the [Julia Discourse forums](https://discourse.julialang.org)
+ See also [SciML Community page](https://sciml.ai/community/)
-
-## Reproducibility
-
-```@raw html
-The documentation of this SciML package was built using these direct dependencies,
-```
-
-```@example
-using Pkg # hide
-Pkg.status() # hide
-```
-
-```@raw html
-
-```
-
-```@raw html
-and using this machine and Julia version.
-```
-
-```@example
-using InteractiveUtils # hide
-versioninfo() # hide
-```
-
-```@raw html
-
-```
-
-```@raw html
-A more complete overview of all dependencies and their versions is also provided.
-```
-
-```@example
-using Pkg # hide
-Pkg.status(; mode = PKGMODE_MANIFEST) # hide
-```
-
-```@raw html
-
-```
-
-```@eval
-using TOML
-using Markdown
-version = TOML.parse(read("../../Project.toml", String))["version"]
-name = TOML.parse(read("../../Project.toml", String))["name"]
-link_manifest = "https://github.com/SciML/" * name * ".jl/tree/gh-pages/v" * version *
- "/assets/Manifest.toml"
-link_project = "https://github.com/SciML/" * name * ".jl/tree/gh-pages/v" * version *
- "/assets/Project.toml"
-Markdown.parse("""You can also download the
-[manifest]($link_manifest)
-file and the
-[project]($link_project)
-file.
-""")
-```
diff --git a/docs/src/interface.md b/docs/src/interface.md
index ae9aaab..d244e79 100644
--- a/docs/src/interface.md
+++ b/docs/src/interface.md
@@ -1,128 +1,80 @@
-# The SurrogateBase Interface
+# Extending SurrogatesBase
-## Deterministic Surrogates
-
-Deterministic surrogates `s` are subtypes of `SurrogatesBase.AbstractDeterministicSurrogate`,
-which is a subtype of `Function`.
-
-### Required methods
-
-The method `update!(s, xs, ys)` **must** be implemented and the surrogate **must** be
-[callable](https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects)
-`s(xs)`, where `xs` is a `Vector` of input points and `ys` is a `Vector` of corresponding evaluations.
+SurrogatesBase defines the small public contract shared by deterministic and stochastic surrogate
+packages. It supplies interface tags and generic functions only; concrete packages own fitting,
+evaluation, posterior representations, and data storage.
-Calling `update!(s, xs, ys)` refits the surrogate `s` to include evaluations `ys` at points `xs`.
-The result of `s(xs)` is a `Vector` of evaluations of the surrogate at points `xs`, corresponding to approximations of the underlying function at points `xs` respectively.
-
-For single points `x` and `y`, call these methods via `update!(s, [x], [y])`
-and `s([x])`.
+## Deterministic Surrogates
-### Optional methods
+A deterministic implementation subtypes [`AbstractDeterministicSurrogate`](@ref) and is callable.
+The minimum interface is:
-If the surrogate `s` wants to expose current parameter values, the method `parameters(s)` **must** be implemented.
+```julia
+(surrogate)(x)
+update!(surrogate, new_x, new_y)
+```
-If the surrogate `s` has tunable hyperparameters, the methods
-`update_hyperparameters!(s, prior)` and `hyperparameters(s)` **must** be implemented.
+`x` can be a scalar, one point, or a batch, according to the concrete package's documented domain.
+The base interface does not impose a container representation. A batch call must preserve the
+correspondence between its inputs and predictions.
-Calling `update_hyperparameters!(s, prior)` updates the hyperparameters of the surrogate `s` by performing hyperparameter optimization using the information in `prior`. After the hyperparameters of `s` are updated, `s` is fit to past evaluations.
-Calling `hyperparameters(s)` returns current values of hyperparameters.
+`update!` is in place. `new_x` and `new_y` represent matching observations: a batch has the same
+number of locations and values in the same order, and a scalar update represents exactly one pair.
+Implementations may return `nothing`, the updated surrogate, or another implementation-specific
+value. Generic code must inspect the mutated surrogate instead of relying on that return value.
-### Example
+An implementation may additionally expose [`parameters`](@ref), [`hyperparameters`](@ref), and
+[`update_hyperparameters!`](@ref). These are optional, so consumers should only call them when a
+concrete surrogate documents support.
```julia
using SurrogatesBase
-struct RBF{T} <: AbstractDeterministicSurrogate
- scale::T
- centers::Vector{T}
- weights::Vector{T}
+mutable struct LinearMock <: AbstractDeterministicSurrogate
+ slope::Float64
end
-(rbf::RBF)(xs) = [rbf.weights' * exp.(-rbf.scale * (x .- rbf.centers).^2)
- for x in xs]
+(surrogate::LinearMock)(x) = surrogate.slope * x
-function SurrogatesBase.update!(rbf::RBF, xs, ys)
- # Refit the surrogate by updating rbf.weights to include new
- # evaluations ys at points xs
- return rbf
-end
-
-SurrogatesBase.parameters(rbf::RBF) = rbf.centers, rbf.weights
-
-SurrogatesBase.hyperparameters(rbf::RBF) = rbf.scale
-
-function SurrogatesBase.update_hyperparameters!(rbf::RBF, prior)
- # update rbf.scale and fit the surrogate by adapting rbf.weights
- return rbf
+function SurrogatesBase.update!(surrogate::LinearMock, new_x, new_y)
+ surrogate.slope = last(new_y) / last(new_x)
+ return nothing
end
```
## Stochastic Surrogates
-Stochastic surrogates `s` are subtypes of `SurrogatesBase.AbstractStochasticSurrogate`.
-
-### Required methods
-
-The methods `update!(s, xs, ys)` and `finite_posterior(s, xs)` **must** be implemented, where `xs` is a `Vector` of input points and `ys` is a `Vector` of corresponding observed samples.
-
-Calling `update!(s, xs, ys)` refits the surrogate `s` to include observations `ys` at points `xs`.
-
-For single points `x` and `y`, call the `update!(s, xs, ys)` via `update!(s, [x], [y])`.
-
-Calling `finite_posterior(s, xs)` returns an object that provides methods for working with the finite
-dimensional posterior distribution at points `xs`.
-The following methods might be supported:
-
-- `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`
-
-### Optional methods
-
-If the surrogate `s` wants to expose current parameter values, the method `parameters(s)` **must** be implemented.
-
-If the surrogate `s` has tunable hyper-parameters, the methods
-`update_hyperparameters!(s, prior)` and `hyperparameters(s)` **must** be implemented.
-
-Calling `update_hyperparameters!(s, prior)` updates the hyperparameters of the surrogate `s` by performing hyperparameter optimization using the information in `prior`. After the hyperparameters of `s` are updated, `s` is fit to past samples.
-Calling `hyperparameters(s)` returns current values of hyperparameters.
+A stochastic implementation subtypes [`AbstractStochasticSurrogate`](@ref) and implements:
+```julia
+update!(surrogate, new_x, new_y)
+finite_posterior(surrogate, xs)
+```
-### Example
+The update rules are the same as for deterministic surrogates. `finite_posterior` receives query
+inputs in a concrete implementation's documented representation and returns that implementation's
+finite-dimensional posterior object. That object must preserve the query ordering and document the
+operations it supports, such as `Statistics.mean`, `Statistics.var`, or `rand`.
```julia
+using Statistics
using SurrogatesBase
-mutable struct GaussianProcessSurrogate{D, R, GP, H <: NamedTuple} <: AbstractStochasticSurrogate
- xs::Vector{D}
- ys::Vector{R}
- gp_process::GP
- hyperparameters::H
+mutable struct ConstantPosteriorSurrogate <: AbstractStochasticSurrogate
+ mean_value::Float64
end
-function SurrogatesBase.update!(g::GaussianProcessSurrogate, new_xs, new_ys)
- append!(g.xs, new_xs)
- append!(g.ys, new_ys)
- # condition the prior `g.gp_process` on new data to obtain a posterior
- # update g.gp_process to the posterior process
- return g
+struct ConstantPosterior
+ means::Vector{Float64}
end
-function SurrogatesBase.finite_posterior(g::GaussianProcessSurrogate, xs)
- # Return a finite dimensional projection of g.gp_process at points xs.
- # The returned object GP_finite supports methods mean(GP_finite) and
- # var(GP_finite) for obtaining the vector of means and variances at points xs.
-end
+Statistics.mean(posterior::ConstantPosterior) = posterior.means
-SurrogatesBase.hyperparameters(g::GaussianProcessSurrogate) = g.hyperparameters
-
-function SurrogatesBase.update_hyperparameters!(g::GaussianProcessSurrogate, prior)
- # Use prior on hyperparameters, e.g., parameters uniformly distributed
- # between an upper and lower bound, to perform hyperparameter optimization.
- # Set g.hyperparameters to the improved hyperparameters.
- # Fit a Gaussian process that uses the updated hyperparameters to past
- # samples and save it in g.gp_process.
- return g
+function SurrogatesBase.finite_posterior(surrogate::ConstantPosteriorSurrogate, xs)
+ return ConstantPosterior(fill(surrogate.mean_value, length(xs)))
end
-```
\ No newline at end of file
+```
+
+The package test suite validates these rules using mock subtypes that are driven only through the
+public SurrogatesBase functions. This keeps the extension contract independent of implementation
+details from any concrete surrogate package.
diff --git a/src/SurrogatesBase.jl b/src/SurrogatesBase.jl
index 915ba50..31aa2a0 100644
--- a/src/SurrogatesBase.jl
+++ b/src/SurrogatesBase.jl
@@ -1,48 +1,71 @@
+"""
+ SurrogatesBase
+
+Common public interfaces for deterministic and stochastic surrogate implementations.
+
+See [`AbstractDeterministicSurrogate`](@ref), [`AbstractStochasticSurrogate`](@ref), and the
+developer interface guide for extension rules.
+"""
module SurrogatesBase
export AbstractDeterministicSurrogate
export AbstractStochasticSurrogate
-
export update!, parameters
export update_hyperparameters!, hyperparameters
export finite_posterior
"""
abstract type AbstractDeterministicSurrogate <: Function end
- (s::AbstractDeterministicSurrogate)(xs)
-Interface tag for deterministic surrogate models.
+Abstract interface for a fitted deterministic surrogate.
+
+`AbstractDeterministicSurrogate` has no fields. Concrete subtypes own their training data,
+fitted state, and domain representation. A subtype is a function and represents a deterministic
+approximation such as a regression model or an interpolant.
-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.
+# Interface
-# Required Methods
+To extend this interface, define a concrete subtype and implement the following methods for that
+subtype:
- - `(s)(xs)`: evaluate the surrogate at the points in `xs`.
- - [`update!(s, new_xs, new_ys)`](@ref): incorporate new observations.
+ - `(surrogate)(x)`: evaluate the fitted approximation at an input `x` supported by the
+ implementation.
+ - [`update!(surrogate, new_x, new_y)`](@ref): incorporate paired observations.
+
+The base interface does not prescribe whether `x` is scalar, a point container, or a batch of
+points. A consuming package may require one of those forms, so implementations must document the
+input forms they support. When an implementation supports batched evaluation, its result must
+preserve the correspondence between requested inputs and returned predictions.
+
+`update!` is an in-place interface: it must leave `surrogate` representing the updated fit. Its
+return value is intentionally unspecified, because existing implementations return `nothing`, the
+surrogate, or an implementation-specific value. Generic callers must use the mutated surrogate and
+must not depend on the return value.
# Optional Methods
- - [`parameters(s)`](@ref): return learned parameter values.
- - [`hyperparameters(s)`](@ref): return tunable hyperparameter values.
- - [`update_hyperparameters!(s, prior)`](@ref): update tunable hyperparameters.
+ - [`parameters(surrogate)`](@ref): expose learned parameters or fitted state.
+ - [`hyperparameters(surrogate)`](@ref): expose tunable fitting configuration.
+ - [`update_hyperparameters!(surrogate, prior)`](@ref): update that configuration in place.
# Examples
```jldoctest
-julia> struct ConstantSurrogate{T} <: AbstractDeterministicSurrogate
+julia> mutable struct ConstantSurrogate{T} <: AbstractDeterministicSurrogate
value::T
end
-julia> (s::ConstantSurrogate)(xs) = fill(s.value, length(xs));
+julia> (surrogate::ConstantSurrogate)(x) = surrogate.value;
+
+julia> function SurrogatesBase.update!(surrogate::ConstantSurrogate, new_x, new_y)
+ surrogate.value = last(new_y)
+ return nothing
+ end;
julia> surrogate = ConstantSurrogate(1.5);
-julia> surrogate([[0.0, 1.0], [1.0, 2.0]])
-2-element Vector{Float64}:
- 1.5
- 1.5
+julia> update!(surrogate, [0.0, 1.0], [2.0, 3.0]); surrogate(0.25)
+3.0
```
"""
abstract type AbstractDeterministicSurrogate <: Function end
@@ -50,185 +73,209 @@ abstract type AbstractDeterministicSurrogate <: Function end
"""
abstract type AbstractStochasticSurrogate end
-Interface tag for stochastic surrogate models.
+Abstract interface for an uncertainty-aware surrogate.
-Subtypes approximate a conditional distribution, stochastic process, or uncertainty-aware
-surrogate from observed data.
+`AbstractStochasticSurrogate` has no fields. Concrete subtypes own their observations, fitted
+state, and posterior representation. A subtype represents a conditional distribution or stochastic
+process approximation rather than a deterministic callable approximation.
-# Required Methods
+# Interface
- - [`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`.
+To extend this interface, define a concrete subtype and implement the following methods for that
+subtype:
-# Optional Methods
+ - [`update!(surrogate, new_x, new_y)`](@ref): incorporate paired observations in place.
+ - [`finite_posterior(surrogate, xs)`](@ref): construct a posterior object for query inputs `xs`.
+
+`xs` is normally a collection of query inputs, but its precise representation is owned by the
+concrete surrogate. The returned posterior object is also implementation-defined. It should expose
+the statistical operations promised by the concrete implementation, such as `Statistics.mean`,
+`Statistics.var`, or `rand`. Its values must correspond to the supplied query inputs.
+
+As for [`AbstractDeterministicSurrogate`](@ref), the `update!` return value is unspecified. Generic
+callers must use the mutated surrogate rather than its return value.
- - [`parameters(s)`](@ref): return learned parameter values.
- - [`hyperparameters(s)`](@ref): return tunable hyperparameter values.
- - [`update_hyperparameters!(s, prior)`](@ref): update tunable hyperparameters.
+# Optional Methods
-See also [`finite_posterior`](@ref).
+ - [`parameters(surrogate)`](@ref): expose learned parameters or fitted state.
+ - [`hyperparameters(surrogate)`](@ref): expose tunable fitting configuration.
+ - [`update_hyperparameters!(surrogate, prior)`](@ref): update that configuration in place.
"""
abstract type AbstractStochasticSurrogate end
"""
- update!(s, new_xs::AbstractVector, new_ys::AbstractVector)
-
-Incorporate observations `new_ys` at points `new_xs` into the surrogate `s`.
+ update!(surrogate, new_x, new_y)
-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.
+Incorporate paired observations into `surrogate` in place.
# Arguments
- - `s`: surrogate to refit or update.
- - `new_xs`: input points to add to `s`.
- - `new_ys`: observed values corresponding to `new_xs`.
+ - `surrogate`: an [`AbstractDeterministicSurrogate`](@ref) or
+ [`AbstractStochasticSurrogate`](@ref) concrete implementation to update.
+ - `new_x`: one input or a batch of input locations accepted by the concrete implementation.
+ - `new_y`: observed value or values paired with `new_x`.
+
+# Interface Contract
-Use `update!(s, eachslice(X; dims = 2), new_ys)` when columns of a matrix `X` are the input
-points.
+Concrete surrogate implementations must extend `update!` for their own subtype. For batched
+updates, `new_x` and `new_y` must encode the same number of observations in the same order. For
+single-observation updates, they must encode one paired input and value. The method must update the
+surrogate state; no particular return value is part of this interface.
# Examples
```jldoctest
julia> mutable struct UpdateExampleSurrogate <: AbstractDeterministicSurrogate
- xs::Vector{Float64}
- ys::Vector{Float64}
+ values::Vector{Float64}
end
-julia> (s::UpdateExampleSurrogate)(xs) = fill(last(s.ys), length(xs));
+julia> (surrogate::UpdateExampleSurrogate)(x) = last(surrogate.values);
-julia> function SurrogatesBase.update!(s::UpdateExampleSurrogate, new_xs, new_ys)
- append!(s.xs, new_xs)
- append!(s.ys, new_ys)
- return s
+julia> function SurrogatesBase.update!(surrogate::UpdateExampleSurrogate, new_x, new_y)
+ append!(surrogate.values, new_y)
+ return nothing
end;
-julia> surrogate = UpdateExampleSurrogate(Float64[], Float64[]);
+julia> surrogate = UpdateExampleSurrogate(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
+julia> update!(surrogate, [1.0, 2.0], [3.0, 4.0]); surrogate(0.0)
+4.0
```
"""
function update! end
"""
- parameters(s)
+ parameters(surrogate)
+
+Return learned parameters or fitted state exposed by `surrogate`.
+
+# Arguments
-Return the current learned parameter values of the surrogate `s`.
+ - `surrogate`: a concrete surrogate implementation that documents this optional method.
-This is an optional interface method for surrogate implementations that expose fitted
-parameters separately from tunable hyperparameters.
+# Returns
+
+An implementation-defined representation of learned parameters or fitted state. The returned object
+may be a scalar, tuple, named tuple, array, or model object.
+
+# Interface Contract
+
+`parameters` is optional. Extend it only for concrete surrogate subtypes that expose learned state;
+calling it for a subtype without a method raises a `MethodError`.
# Examples
```jldoctest
julia> struct ParameterExampleSurrogate <: AbstractDeterministicSurrogate
- weights::Vector{Float64}
+ weight::Float64
end
-julia> (s::ParameterExampleSurrogate)(xs) = fill(sum(s.weights), length(xs));
+julia> (surrogate::ParameterExampleSurrogate)(x) = surrogate.weight * x;
-julia> SurrogatesBase.parameters(s::ParameterExampleSurrogate) = s.weights;
+julia> SurrogatesBase.parameters(surrogate::ParameterExampleSurrogate) = (; weight = surrogate.weight);
-julia> parameters(ParameterExampleSurrogate([1.0, 2.0]))
-2-element Vector{Float64}:
- 1.0
- 2.0
+julia> parameters(ParameterExampleSurrogate(2.0))
+(weight = 2.0,)
```
"""
function parameters end
"""
- update_hyperparameters!(s, prior)
-
-Update tunable hyperparameters of the surrogate `s` using information in `prior`.
+ update_hyperparameters!(surrogate, 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.
+Update the tunable fitting configuration of `surrogate` in place.
# Arguments
- - `s`: surrogate whose hyperparameters are updated.
- - `prior`: implementation-defined prior, bounds, or configuration used by the update.
+ - `surrogate`: a concrete surrogate implementation with tunable hyperparameters.
+ - `prior`: implementation-defined prior, bounds, or optimization configuration.
+
+# Interface Contract
+
+This optional method must leave `surrogate` consistent with its updated hyperparameters. If those
+hyperparameters affect the fitted representation, the implementation must refit or invalidate that
+representation before subsequent evaluation. The return value is not part of the interface.
# Examples
```jldoctest
-julia> mutable struct HyperparameterUpdateExample <: AbstractDeterministicSurrogate
+julia> mutable struct HyperparameterExample <: AbstractDeterministicSurrogate
scale::Float64
end
-julia> (s::HyperparameterUpdateExample)(xs) = fill(s.scale, length(xs));
+julia> (surrogate::HyperparameterExample)(x) = surrogate.scale * x;
-julia> function SurrogatesBase.update_hyperparameters!(s::HyperparameterUpdateExample, prior)
- s.scale = (s.scale + prior.scale) / 2
- return s
+julia> function SurrogatesBase.update_hyperparameters!(surrogate::HyperparameterExample, prior)
+ surrogate.scale = prior.scale
+ return nothing
end;
-julia> surrogate = HyperparameterUpdateExample(2.0);
-
-julia> update_hyperparameters!(surrogate, (; scale = 4.0)) === surrogate
-true
+julia> surrogate = HyperparameterExample(2.0);
-julia> surrogate.scale
-3.0
+julia> update_hyperparameters!(surrogate, (; scale = 4.0)); surrogate(0.5)
+2.0
```
-
-See also [`hyperparameters`](@ref).
"""
function update_hyperparameters! end
"""
- hyperparameters(s)
+ hyperparameters(surrogate)
+
+Return the tunable fitting configuration exposed by `surrogate`.
-Return the current tunable hyperparameter values of the surrogate `s`.
+# Arguments
+
+ - `surrogate`: a concrete surrogate implementation that documents this optional method.
+
+# Returns
-This is an optional interface method for surrogate implementations with configuration
-values that control fitting or posterior construction.
+An implementation-defined representation of tunable configuration, commonly a named tuple or a
+small immutable configuration object.
+
+# Interface Contract
+
+`hyperparameters` is optional. When both this method and [`update_hyperparameters!`](@ref) are
+implemented, the returned configuration must describe the setting used for subsequent evaluations.
# Examples
```jldoctest
julia> struct HyperparameterReadExample <: AbstractDeterministicSurrogate
- settings::NamedTuple
+ scale::Float64
end
-julia> (s::HyperparameterReadExample)(xs) = fill(s.settings.scale, length(xs));
+julia> (surrogate::HyperparameterReadExample)(x) = surrogate.scale * x;
-julia> SurrogatesBase.hyperparameters(s::HyperparameterReadExample) = s.settings;
+julia> SurrogatesBase.hyperparameters(surrogate::HyperparameterReadExample) =
+ (; scale = surrogate.scale);
-julia> hyperparameters(HyperparameterReadExample((; scale = 2.0)))
+julia> hyperparameters(HyperparameterReadExample(2.0))
(scale = 2.0,)
```
-
-See also [`update_hyperparameters!`](@ref).
"""
function hyperparameters end
"""
- finite_posterior(s::AbstractStochasticSurrogate, xs::AbstractVector)
+ finite_posterior(surrogate, xs)
+
+Construct the finite-dimensional posterior represented by `surrogate` at query inputs `xs`.
+
+# Arguments
+
+ - `surrogate`: an [`AbstractStochasticSurrogate`](@ref) concrete implementation.
+ - `xs`: query inputs in a representation accepted by that implementation.
-Return a finite-dimensional posterior object at points `xs`.
+# Returns
-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:
+An implementation-defined posterior object for the requested query inputs. Concrete implementations
+must document the statistical operations they support, for example `Statistics.mean`,
+`Statistics.var`, or `rand`.
- - `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`.
+# Interface Contract
-Use `mean(finite_posterior(s, eachslice(X; dims = 2)))` when columns of a matrix `X` are
-the input points.
+Concrete stochastic surrogate implementations must extend this function for their own subtype. The
+posterior must preserve correspondence with `xs`; for a batch, the returned statistics and samples
+must use the same query ordering. This function has no fallback implementation.
# Examples
@@ -243,10 +290,10 @@ julia> struct PosteriorExample
means::Vector{Float64}
end
-julia> Statistics.mean(p::PosteriorExample) = p.means;
+julia> Statistics.mean(posterior::PosteriorExample) = posterior.means;
-julia> function SurrogatesBase.finite_posterior(s::PosteriorExampleSurrogate, xs)
- return PosteriorExample(fill(s.value, length(xs)))
+julia> function SurrogatesBase.finite_posterior(surrogate::PosteriorExampleSurrogate, xs)
+ PosteriorExample(fill(surrogate.value, length(xs)))
end;
julia> posterior = finite_posterior(PosteriorExampleSurrogate(1.25), [0.0, 1.0]);
diff --git a/test/developer_interfaces.jl b/test/developer_interfaces.jl
new file mode 100644
index 0000000..f39d578
--- /dev/null
+++ b/test/developer_interfaces.jl
@@ -0,0 +1,72 @@
+using SurrogatesBase
+
+using Statistics
+using Test
+
+mutable struct DeterministicMockSurrogate <: AbstractDeterministicSurrogate
+ slope::Float64
+ offset::Float64
+end
+
+(surrogate::DeterministicMockSurrogate)(x::Real) = surrogate.slope * x + surrogate.offset
+
+function SurrogatesBase.update!(
+ surrogate::DeterministicMockSurrogate, new_x, new_y
+ )
+ surrogate.offset = last(new_y) - surrogate.slope * last(new_x)
+ return nothing
+end
+
+SurrogatesBase.parameters(surrogate::DeterministicMockSurrogate) =
+ (; slope = surrogate.slope, offset = surrogate.offset)
+SurrogatesBase.hyperparameters(surrogate::DeterministicMockSurrogate) = (; slope = surrogate.slope)
+
+function SurrogatesBase.update_hyperparameters!(
+ surrogate::DeterministicMockSurrogate, prior
+ )
+ surrogate.slope = prior.slope
+ return nothing
+end
+
+mutable struct StochasticMockSurrogate <: AbstractStochasticSurrogate
+ mean_value::Float64
+end
+
+struct MockPosterior
+ means::Vector{Float64}
+end
+
+Statistics.mean(posterior::MockPosterior) = posterior.means
+
+function SurrogatesBase.update!(surrogate::StochasticMockSurrogate, new_x, new_y)
+ surrogate.mean_value = sum(new_y) / length(new_y)
+ return nothing
+end
+
+SurrogatesBase.parameters(surrogate::StochasticMockSurrogate) = (; mean = surrogate.mean_value)
+
+function SurrogatesBase.finite_posterior(surrogate::StochasticMockSurrogate, xs)
+ return MockPosterior(fill(surrogate.mean_value, length(xs)))
+end
+
+@testset "Public surrogate extension contracts" begin
+ @testset "Deterministic surrogate" begin
+ surrogate = DeterministicMockSurrogate(2.0, 1.0)
+
+ @test surrogate(3.0) == 7.0
+ @test isnothing(update!(surrogate, [1.0, 2.0], [5.0, 7.0]))
+ @test surrogate(2.0) == 7.0
+ @test parameters(surrogate) == (; slope = 2.0, offset = 3.0)
+ @test hyperparameters(surrogate) == (; slope = 2.0)
+ @test isnothing(update_hyperparameters!(surrogate, (; slope = 4.0)))
+ @test surrogate(2.0) == 11.0
+ end
+
+ @testset "Stochastic surrogate" begin
+ surrogate = StochasticMockSurrogate(0.0)
+
+ @test isnothing(update!(surrogate, [1.0, 2.0], [2.0, 6.0]))
+ @test parameters(surrogate) == (; mean = 4.0)
+ @test mean(finite_posterior(surrogate, [0.0, 1.0, 2.0])) == fill(4.0, 3)
+ end
+end
diff --git a/test/qa/Project.toml b/test/qa/Project.toml
index 769851f..54cf9f0 100644
--- a/test/qa/Project.toml
+++ b/test/qa/Project.toml
@@ -1,19 +1,9 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
-JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
-LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
-SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
-Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
SurrogatesBase = "89f642e6-4179-4274-8202-c11f4bd9a72c"
-Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[compat]
Aqua = "0.8"
-JET = "0.9, 0.10, 0.11"
-LinearAlgebra = "1.10"
-SafeTestsets = "0.1"
-SciMLTesting = "2.1"
-Statistics = "1.10"
-Test = "1.10"
+SciMLTesting = "2.4"
julia = "1.10"
diff --git a/test/qa/qa.jl b/test/qa/qa.jl
index 685f1ea..c13a8f4 100644
--- a/test/qa/qa.jl
+++ b/test/qa/qa.jl
@@ -1,96 +1,3 @@
-using SciMLTesting
-using SurrogatesBase
-using JET
-using LinearAlgebra
-using Test
-import Statistics
+using SciMLTesting, SurrogatesBase
run_qa(SurrogatesBase)
-
-# 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)
-# stays inferable for downstream subtypes.
-#
-# On Julia 1.12+, LinearAlgebra.norm_recursive_check has a type inference issue
-# that surfaces as JET false positives through `norm`. We ignore LinearAlgebra
-# and Base frames to filter those stdlib issues while still checking our own code.
-const JET_CONFIG = (
- ignored_modules = (
- JET.AnyFrameModule(LinearAlgebra),
- JET.AnyFrameModule(Base),
- ),
-)
-
-@testset "JET report_call type stability" begin
- @testset "DummySurrogate type stability" begin
- struct JETDummySurrogate{X, Y} <: AbstractDeterministicSurrogate
- xs::Vector{X}
- ys::Vector{Y}
- end
- (s::JETDummySurrogate)(x) = s.ys[argmin([norm(x - ξ) for ξ in s.xs])]
- function SurrogatesBase.update!(s::JETDummySurrogate, new_xs, new_ys)
- append!(s.xs, new_xs)
- append!(s.ys, new_ys)
- end
-
- d = JETDummySurrogate(Vector{Vector{Float64}}(), Vector{Int}())
- SurrogatesBase.update!(d, [[10.3, 0.1], [1.9, 2.1]], [5, 6])
-
- result = JET.report_call(d, Tuple{Vector{Float64}}; JET_CONFIG...)
- @test length(JET.get_reports(result)) == 0
-
- result = JET.report_call(
- SurrogatesBase.update!,
- Tuple{
- JETDummySurrogate{Vector{Float64}, Int}, Vector{Vector{Float64}},
- Vector{Int},
- }; JET_CONFIG...
- )
- @test length(JET.get_reports(result)) == 0
- end
-
- @testset "Stochastic surrogate type stability" begin
- mutable struct JETDummyStochasticSurrogate{X, Y} <: AbstractStochasticSurrogate
- xs::Vector{X}
- ys::Vector{Y}
- ys_mean::Y
- end
- function SurrogatesBase.update!(s::JETDummyStochasticSurrogate, new_xs, new_ys)
- append!(s.xs, new_xs)
- append!(s.ys, new_ys)
- s.ys_mean = (s.ys_mean * (length(s.xs) - length(new_xs)) + sum(new_ys)) /
- length(s.xs)
- end
- SurrogatesBase.parameters(s::JETDummyStochasticSurrogate) = s.ys_mean
-
- struct JETFiniteDummyStochasticSurrogate{X}
- means::Vector{X}
- end
- Statistics.mean(s::JETFiniteDummyStochasticSurrogate) = s.means
- function JETFiniteDummyStochasticSurrogate(s, xs)
- return JETFiniteDummyStochasticSurrogate(s.ys_mean .* ones(length(xs)))
- end
- function SurrogatesBase.finite_posterior(s::JETDummyStochasticSurrogate, xs)
- JETFiniteDummyStochasticSurrogate(s, xs)
- end
-
- result = JET.report_call(
- SurrogatesBase.update!,
- Tuple{
- JETDummyStochasticSurrogate{Vector{Float64}, Float64},
- Vector{Vector{Float64}}, Vector{Float64},
- }; JET_CONFIG...
- )
- @test length(JET.get_reports(result)) == 0
-
- result = JET.report_call(
- SurrogatesBase.finite_posterior,
- Tuple{
- JETDummyStochasticSurrogate{Vector{Float64}, Float64},
- Vector{Vector{Float64}},
- }; JET_CONFIG...
- )
- @test length(JET.get_reports(result)) == 0
- end
-end