Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eval_equation and tests #74

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
55 changes: 55 additions & 0 deletions src/evaluation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,61 @@ function eval_RJ(point::Matrix{Float64}, slmed::SelectiveLinearizationMED)
return res, jac
end

"""
eval_equation(model::AbstractModel, eqn::AbstractEquation, sim_data::AbstractMatrix{Float64}, rng::UnitRange{Int64} = 1:size(sim_data,1))

Evaluate the residuals of a given equation over a range of time points.

This function calculates the residuals of the provided equation `eqn` for each time step in the range `rng` from the simulated data `sim_data`. The model's lag and lead structure is respected during evaluation.

# Arguments
- `model::AbstractModel`: The model containing the equation to be evaluated.
- `eqn::AbstractEquation`: The equation for which residuals are to be calculated.
- `sim_data::AbstractMatrix{Float64}`: The simulated data, with rows representing time points and columns representing model.allvars (variables, shocks and auxiliary variables).
- `rng::UnitRange{Int64}`: The range of time points over which to evaluate the equation. By default, evaluates over all time points in `sim_data`.

# Returns
- `res::Vector{Float64}`: A vector of residuals for each time point in the range `rng`. Entries for time points where residuals cannot be computed (due to insufficient lags or leads) are filled with `NaN`.
"""
function eval_equation(model::AbstractModel, eqn::AbstractEquation, sim_data::AbstractMatrix{Float64}, rng::UnitRange{Int64} = 1:size(sim_data,1))
# Check bounds
@assert rng[begin] >= 1 && rng[end] <= size(sim_data, 1) "Error: The range specified is out of bounds. Ensure that the range starts from 1 or higher and ends within the size of the data."

Comment on lines +515 to +518
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two comments about @assert

  • As a general rule I wouldn't use an assertion to check validity of input arguments. I use assertions to confirm that something I assume is actually true. I think throwing an ArgumentError is more appropriate when input arguments are not valid. A BoundsError would be okay here too, since this is a bounds check.
  • On the other hand, in this function we return NaN at indexes of res which depend on rows of sim_data that are out of bounds. So this function doesn't actually fail if rng is "not valid", it just fills those locations with NaNs. So why bother with a bounds check at all.

# Map the model variables to their respective indices
var_to_idx = _make_var_to_idx(model.allvars)

# Calculate t_start based on the model's maximum lag
t_start = 1 + model.maxlag

# Create index mapping for the equation's time series references
inds = [CartesianIndex((t_start + ti, var_to_idx[var])) for (var, ti) in keys(eqn.tsrefs)]

# Account for steady state values in case they are used
ed = DynEqnEvalData(eqn, model, var_to_idx)

# Initialize the residual vector with NaN values
res = fill(NaN, length(rng))

# Iterate over the specified time range
for (idx, t) = enumerate(rng)
# Define the range of data points required for evaluation, including lags and leads
rng_sub = t - model.maxlag : t + model.maxlead

# Ensure the subrange is within bounds of the data
if rng_sub[begin] >= 1 && rng_sub[end] <= size(sim_data, 1)
# Extract the relevant data points for the current time step
point = @view sim_data[rng_sub, :]

# Evaluate the residual for the current data point using the evaluation data structure
res[idx] = eval_resid(eqn, point[inds], ed)
end
end

# Return the vector of residuals
return res
end
export eval_equation

"""
selective_linearize!(model)

Expand Down
28 changes: 28 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1743,4 +1743,32 @@ end
true
end
end

@testset "eval_equation" begin
model = Model()
@parameters model p = 0.1
@variables model x
@shocks model x_shk
@equations model begin
:EQ01 => x[t] = (1-0.50)*@sstate(x) + 0.25*x[t-1] + 0.25*x[t+1] + x_shk[t]
end
@initialize model
@steadystate model x = 2.0
model.sstate.x.level = 2.0
sim_data = [0.5 0.0;
1.5980861244019138 0.0;
1.8923444976076556 0.0;
1.9712918660287082 0.0;
1.992822966507177 0.0;
2.0 0.0]

eqtn = model.equations[:EQ01]
res = eval_equation(model, eqtn, sim_data)
@test isnan(res[1]) && isapprox(res[2:5],[0.0, 0.0, 0.0, 0.0]; atol=1e-12) && isnan(res[6])

sim_data[3,1] += 1
@test eval_equation(model, eqtn, sim_data,3:4) ≈ [1.0, -0.25]

@test_throws AssertionError eval_equation(model, eqtn, sim_data, 1:7)
end
nothing
Loading