Skip to content

Commit

Permalink
Merge pull request #26 from ggebbie/23-source-water-inversion-with-mu…
Browse files Browse the repository at this point in the history
…ltiple-state-variables-bh

23 source water inversion with multiple state variables bh
  • Loading branch information
ggebbie authored Apr 16, 2023
2 parents 385553e + 1ceb3b0 commit 8bc85c5
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 10 deletions.
62 changes: 56 additions & 6 deletions src/BLUEs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module BLUEs

using LinearAlgebra, Statistics, Unitful, UnitfulLinearAlgebra, Measurements
using DimensionalData
using Revise

export Estimate, DimEstimate, OverdeterminedProblem, UnderdeterminedProblem
export solve, show, cost, datacost, controlcost
Expand Down Expand Up @@ -101,6 +102,19 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, x::Union{DimEstimate,Est
# show(io, mime, F.V)
end

function show(io::IO, mime::MIME{Symbol("text/plain")}, x::DimArray{Quantity{Float64}, 3})
summary(io, x); println(io)
statevars = x.dims[3]
for (i, s) in enumerate(statevars)
if i != 1
println()
end

println(io, "State Variable " * string(i) * ": " * string(s))
show(io, mime, x[:,:,At(s)])
end
end

"""
function getproperty(x::Estimate, d::Symbol)
Expand Down Expand Up @@ -473,12 +487,12 @@ function impulseresponse(funk::Function,x,args...)
y₀ = funk(x,args...)
#Eunits = expectedunits(y₀,x)
#Eu = Quantity.(zeros(length(y₀),length(x)),Eunits)

Ep = zeros(length(y₀),length(x))
Ep = zeros(length(y₀), length(x))

for rr in eachindex(x)
#u = zeros(length(x)).*unit.(x)[:]
if length(x) > 1
u = Quantity.(zeros(length(x)),unit.(vec(x)))
u = Quantity.(zeros(length(x)), unit.(vec(x)))
else
u = Quantity(0.0,unit(x))
end
Expand All @@ -489,7 +503,9 @@ function impulseresponse(funk::Function,x,args...)
y = funk(x₁,args...)
#println(y)
if y isa AbstractDimArray
Ep[:,rr] .= vec(parent((y - y₀)/Δu))
#Ep[:,rr] .= vec(parent((y - y₀)/Δu))
Ep[:, rr] .= ustrip.(vec(parent((y - y₀)/Δu)))

else
tmp = ustrip.((y - y₀)/Δu)
# getting ugly around here
Expand All @@ -500,7 +516,8 @@ function impulseresponse(funk::Function,x,args...)
end
end
end



# This function could use vcat to be cleaner (but maybe slower)
# Note: Need to add ability to return sparse matrix
#return E = UnitfulMatrix(Ep,unit.(vec(y₀)),unit.(vec(x)))
Expand Down Expand Up @@ -559,10 +576,44 @@ function convolve(x::AbstractDimArray,E::AbstractDimArray)
lags = first(dims(E))
return sum([E[ii,:] x[Near(tnow-ll),:] for (ii,ll) in enumerate(lags)])
end
"""
function convolve(x::AbstractDimArray, E::AbstractDimArray, coeffs::UnitfulMatrix}
the `coeffs` argument signifies that x is a 3D array (i.e. >1 state variables)
this function both convolves, and linearly combines the propagated state variables
"""
function convolve(x::AbstractDimArray,E::AbstractDimArray, coeffs::UnitfulMatrix)
statevars = x.dims[3]
mat = UnitfulMatrix(transpose([convolve(x[:,:,At(s)], E) for s in statevars])) * coeffs
return getindexqty(mat, 1,1)
end

function convolve(x::AbstractDimArray,E::AbstractDimArray,t::Number)
lags = first(dims(E))
return sum([E[ii,:] x[Near(t-ll),:] for (ii,ll) in enumerate(lags)])
end

#coeffs signifies that x is 3D
function convolve(x::AbstractDimArray, E::AbstractDimArray, t::Number, coeffs::UnitfulMatrix)
statevars = x.dims[3]
mat = UnitfulMatrix(transpose([convolve(x[:,:,At(s)], E, t) for s in statevars]))*coeffs
return getindexqty(mat, 1,1)
end

#don't handle the ndims(M) == 3 case here but I'll get back to it
function convolve(x::AbstractDimArray, M::AbstractDimArray, Tx::Union{Ti, Vector}, coeffs::UnitfulMatrix)
if ndims(M) == 2
return DimArray([convolve(x,M,Tx[tt], coeffs) for (tt, yy) in enumerate(Tx)], Tx)
elseif ndims(M) == 3
error("some code should go here")
else
error("M has wrong number of dims")
end


end


function convolve(x::AbstractDimArray,M::AbstractDimArray,Tx::Union{Ti,Vector})
if ndims(M) == 2
return DimArray([convolve(x,M,Tx[tt]) for (tt,yy) in enumerate(Tx)],Tx)
Expand Down Expand Up @@ -626,5 +677,4 @@ function addcontrol!(x::AbstractDimArray,u)
end
return x
end

end # module
114 changes: 110 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,15 @@ include("test_functions.jl")
y = predictobs(convolve,x,M)

## invert for y for x̃
# Given, M and y. Make first guess for x.
# Given, M and y. Make first guess for x.x
# add adjustment
# DimArray is good enough. This is an array, not necessarily a matrix.
x₀ = DimArray(zeros(size(x))K,(Ti(years),last(dims(M))))

# probe to get E matrix. Use function convolve
E = impulseresponse(convolve,x₀,M)


@test (E*UnitfulMatrix(vec(x₀)))[1] .== ustrip(convolve(x₀, M))
# Does E matrix work properly?
= E*UnitfulMatrix(vec(x))
@test isapprox(y,getindexqty(ỹ,1))
Expand All @@ -282,9 +283,9 @@ include("test_functions.jl")
σₙ = 0.01
σₓ = 100.0

Cnn = UnitfulMatrix(Diagonal(fill(σₙ,length(y))),fill(unit.(y).^1,length(y)),fill(unit.(y).^-1,length(y)),exact=false)
Cnn = UnitfulMatrix(Diagonal(fill(σₙ^2,length(y))),fill(unit.(y).^1,length(y)),fill(unit.(y).^-1,length(y)),exact=false)

Cxx = UnitfulMatrix(Diagonal(fill(σₓ,length(x₀))),unit.(x₀)[:],unit.(x₀)[:].^-1,exact=false)
Cxx = UnitfulMatrix(Diagonal(fill(σₓ^2,length(x₀))),unit.(x₀)[:],unit.(x₀)[:].^-1,exact=false)

problem = UnderdeterminedProblem(UnitfulMatrix([y]),E,Cnn,Cxx,x₀)
= solve(problem)
Expand All @@ -293,6 +294,108 @@ include("test_functions.jl")
@test cost(x̃,problem) < 5e-2 # no noise in ob
end

@testset "source water inversion: obs at one time, many surface regions, with circulation lag, TWO STATE VARIABLES" begin

@dim YearCE "years Common Era"
@dim SurfaceRegion "surface location"
@dim InteriorLocation "interior location"
@dim StateVariable "state variable"

yr = u"yr"
= 5 # how much of a lag is possible?
lags = (0:(nτ-1))yr
surfaceregions = [:NATL,:ANT,:SUBANT]
years = (1990:2000)yr
n = length(surfaceregions)

M = source_water_matrix_with_lag(surfaceregions,lags,years)

statevariables = [, :d18O]
x = source_water_solution(surfaceregions,years, statevariables)

coeffs = UnitfulMatrix(reshape(rand(2), (2,1)), [unit(1.0), K], [K*permil^-1])
y = convolve(x, M, coeffs)

x₀ = copy(x).*0
E = impulseresponse(convolve,x₀,M,coeffs)

#E matrix tests from prior example
= E*UnitfulMatrix(vec(x))
@test isapprox(y,getindexqty(ỹ,1))

#had to get a little crafty to make this work...
= E\UnitfulMatrix(reshape([ustrip.(y)], (1,1)), [permil], [unit(1.0)])
@test isapprox(y,getindexqty(E*x̂,1,1))

σₙ = 0.01
σₓ = 100.0

Cnn = UnitfulMatrix(Diagonal(fill(σₙ.^2,length(y))),fill(unit(y).^1,length(y)),fill(unit(y).^-1,length(y)),exact=false)

Cxx = UnitfulMatrix(Diagonal(fill(σₓ.^2,length(x₀))),unit.(x₀)[:],unit.(x₀)[:].^-1,exact=false)
problem = UnderdeterminedProblem(UnitfulMatrix([y]), E, Cnn, Cxx, x₀)
= solve(problem)
@test within(y, getindexqty(E*.v, 1), 3σₙ)
@test cost(x̃,problem) < 5e-2
@test cost(x̃, problem) == datacost(x̃, problem) + controlcost(x̃, problem)
end

@testset "source water inversion: obs TIMESERIES, many surface regions, with circulation lag, TWO STATE VARIABLES" begin

@dim YearCE "years Common Era"
@dim SurfaceRegion "surface location"
@dim InteriorLocation "interior location"
@dim StateVariable "state variable"

yr = u"yr"
= 5 # how much of a lag is possible?
lags = (0:(nτ-1))yr
surfaceregions = [:NATL,:ANT,:SUBANT]
years = (1990:2000)yr
n = length(surfaceregions)

M = source_water_matrix_with_lag(surfaceregions,lags,years)

statevariables = [, :d18O]
x = source_water_solution(surfaceregions,years, statevariables)

Tx = first(dims(x))
x₀ = copy(x).*0

#choose random linear coefficients
coeffs = UnitfulMatrix(reshape(rand(2), (2,1)), [unit(1.0), K], [K*permil^-1])

#synthetic timeseries
y = convolve(x, M, Tx, coeffs)
E = impulseresponse(convolve, x₀, M, Tx,coeffs)

# Does E matrix work properly?
= E*UnitfulMatrix(vec(x))
for jj in eachindex(vec(y))
@test isapprox.(vec(y)[jj],getindexqty(ỹ,jj))
end
= E\UnitfulMatrix(vec(y))
for jj in eachindex(vec(y))
@test isapprox.(vec(y)[jj],getindexqty(E*x̂,jj))
end

#generate Cnn and Cxx matrices
σₙ = 0.01
σₓ = 100.0
Cnn = UnitfulMatrix(Diagonal(fill(σₙ.^2,length(y))),vec(unit.(y)).^1,vec(unit.(y)).^-1,exact=true)
Cxx = UnitfulMatrix(Diagonal(fill(σₓ.^2,length(x₀))),vec(unit.(x₀)),vec(unit.(x₀)).^-1,exact=true)

#create problem and solve
problem = UnderdeterminedProblem(UnitfulMatrix(vec(y)),E,Cnn,Cxx,x₀)
= solve(problem)
for jj in eachindex(vec(y))
@test within(y[jj],getindexqty(E*.v,jj),3σₙ) # within 3-sigma
end

@test cost(x̃,problem) < 0.5 # ad-hoc choice

end

@testset "source water inversion: obs TIMESERIES, many surface regions, with circulation lag" begin

@dim YearCE "years Common Era"
Expand Down Expand Up @@ -346,6 +449,9 @@ include("test_functions.jl")
# no noise in obs but some control penalty
@test cost(x̃,problem) < 0.5 # ad-hoc choice

@test cost(x̃, problem) == datacost(x̃, problem) + controlcost(x̃, problem)


end

@testset "source water inversion: MANY OBS TIMESERIES, many surface regions, with circulation lag" begin
Expand Down
11 changes: 11 additions & 0 deletions test/test_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ function source_water_solution(surfaceregions,years)
return x
end

function source_water_solution(surfaceregions, years, statevar)
yr = u"yr"
K = u"K"
permil = u"permille"
m = length(years)
n = length(surfaceregions)
mat = cat(randn(m, n, 1)K, randn(m, n, 1)permil; dims = 3)
x = DimArray(mat, (Ti(years), SurfaceRegion(surfaceregions), StateVariable(statevar)))
return x
end

"""
function source_water_matrix_vector_pair_with_lag(M)
Expand Down

0 comments on commit 8bc85c5

Please sign in to comment.