Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 4, 2025

  • Understand current implementation of get_statistics and parse_variables_input_to_index
  • Extend Symbol_input and String_input type definitions to support Vector{Vector{Symbol}}, Vector{Vector{String}}, Vector{Tuple}, and Tuple{Tuple} formats
  • Modify parse_variables_input_to_index to handle nested vector and tuple input for covariance groups
  • Add helper functions is_grouped_covariance_input and parse_covariance_groups
  • Update get_statistics to handle grouped covariance indices
  • Update covariance computation code to extract only specified groups
  • Add tests for the new grouped covariance functionality
  • Fix String_input nested vector parsing
  • Add documentation for the grouped covariance feature
  • Verify syntax and implementation correctness
  • Address code review feedback and simplify helper functions
  • Fix documentation wording
  • Change return format to single matrix with zeros for non-computed covariances
  • Optimize higher order propagation to reduce allocations
  • Further optimize determine_efficient_order to minimize all allocations
  • Update documentation example to use balanced groups
  • Add support for vector of tuples and tuple of tuples formats
  • Simplify grouped covariance indicator check
  • Apply vectorized approach to all remaining nested loops in determine_efficient_order

Summary

All optimizations completed. The vectorized approach using reshape and sum operations is now consistently applied throughout all determine_efficient_order functions (first, second, and third order) for both observable and covariance variable dependency detection. All nested loops for dependency detection have been eliminated, and propagation loops use precomputed matrix slices with no copy() calls.

Original prompt

in the get_statistics function, the covariance argument allow for the input to be Vector of Vectors of either Symbols or Strings so that you specify groups of variables to compute the covariance for. this allows more granular control over which covariances to compute. so you can say covariance = [[:y, :c], [:k, :i]] to compute covariances for y and c together and k and i together, but not between y and k for example. this would require modifying the parsing function to handle this new input format. = Symbol[], and modifying the rest of the code accordingly.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Update get_statistics function to handle vector of vectors for covariance Add grouped covariance input to get_statistics Nov 4, 2025
Copilot AI requested a review from thorek1 November 4, 2025 23:13
Copilot finished work on behalf of thorek1 November 4, 2025 23:13
Copy link
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

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

@copilot return one matrix only where you then leave blank the covariances not calculated (instead of now returning as many matrices as there are entries in the vector)

Copy link
Contributor Author

Copilot AI commented Nov 4, 2025

@copilot return one matrix only where you then leave blank the covariances not calculated (instead of now returning as many matrices as there are entries in the vector)

Changed to return a single matrix where cross-group covariances are set to zero. The implementation now fills only the specified group blocks and leaves zeros elsewhere. Commit: e949bfd

Copilot AI requested a review from thorek1 November 4, 2025 23:32
Copilot finished work on behalf of thorek1 November 4, 2025 23:32
Copy link
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

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

@copilot Add the define_efficient_order logic to the second and first order calculations as well (currently it's only there for third order)

Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot Add the define_efficient_order logic to the second and first order calculations as well (currently it's only there for third order)

I understand you want to add the determine_efficient_order optimization to first and second order calculations (similar to how it's currently used in third order). This would involve:

  1. Adding observables and covariance parameters to calculate_covariance and calculate_second_order_moments_with_covariance
  2. Using determine_efficient_order to compute only necessary dependencies
  3. Updating get_statistics to pass these parameters

This is a substantial refactoring. Should I:

  • Maintain backward compatibility by making these parameters optional with defaults that compute everything (like current behavior)?
  • Or create new function variants specifically for the optimized computation?

Also, do you want this optimization applied even when all variables are requested, or only when specific subsets are requested?

Copilot AI requested a review from thorek1 November 5, 2025 00:23
Copilot finished work on behalf of thorek1 November 5, 2025 00:23
Copy link
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

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

@copilot The higher order propagation code generates too many allocations. Instead of the loop generate the vector to be multiplied with the solution to get the affected states

Replace nested loops with vectorized operations using Kronecker product selectors. Instead of iterating through all column indices and checking conditions in nested loops, generate a selector vector that identifies relevant columns where all states are dependencies, then multiply with the solution matrix once.

Co-authored-by: thorek1 <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot The higher order propagation code generates too many allocations. Instead of the loop generate the vector to be multiplied with the solution to get the affected states

Optimized the higher order propagation code by replacing nested loops with vectorized operations. Instead of iterating through all column indices, the code now generates a selector vector using Kronecker products (e.g., kron(prev_dependencies, prev_dependencies) for second order) to identify relevant columns in one operation, then processes them with a single matrix operation. This eliminates many intermediate allocations from the loop-based approach. Commit: 18d77b5

Copilot AI changed the title Add grouped covariance input to get_statistics Add grouped covariance input to get_statistics and optimize higher order propagation Nov 5, 2025
Copilot AI requested a review from thorek1 November 5, 2025 23:16
Copilot finished work on behalf of thorek1 November 5, 2025 23:16
Copy link
Owner

@thorek1 thorek1 left a comment

Choose a reason for hiding this comment

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

@copilot Think about other ways to minimise allocations in the determine_efficient_order functions and implement them

Key optimizations:
1. Precompute repeated indexin and matrix slicing operations outside loops
2. Replace copy(dependencies_in_states) with direct assignment to avoid allocations
3. Vectorize nested loops for second/third order dependency detection using reshape and sum operations
4. Build lookup dictionary for covariance variable searches instead of repeated findfirst calls
5. Reuse precomputed matrix slices in propagation loops
6. Eliminate redundant while loop condition checks

Co-authored-by: thorek1 <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot Think about other ways to minimise allocations in the determine_efficient_order functions and implement them

Implemented additional optimizations to minimize allocations in determine_efficient_order:

  1. Precomputed operations: Moved repeated indexin() and matrix slicing outside loops (e.g., 𝐒₁_states, 𝐒₂_states, 𝐒₃_states)
  2. Eliminated copy() calls: Replaced copy(dependencies_in_states) with direct variable assignment to avoid unnecessary allocations
  3. Vectorized nested loops: Replaced nested loops for second/third order dependency detection with reshape + sum operations along dimensions
  4. Lookup dictionary: Added var_to_idx dictionary for O(1) covariance variable lookups instead of repeated findfirst() calls
  5. Reused computed slices: Matrix slices computed once and reused in propagation loops
  6. Simplified loop logic: Changed while loop to avoid redundant comparisons

These changes eliminate allocation hotspots while maintaining the same functionality. Commit: 8bc6672

Copilot AI changed the title Add grouped covariance input to get_statistics and optimize higher order propagation Add grouped covariance input to get_statistics and optimize determine_efficient_order Nov 5, 2025
Copilot AI requested a review from thorek1 November 5, 2025 23:52
Copilot finished work on behalf of thorek1 November 5, 2025 23:52
:standard_deviation => [0.0266642, 0.264677, 0.0739325, 0.0102062]
# For grouped covariance (computing covariances only within specified groups):
get_statistics(RBC, RBC.parameter_values, covariance = [[:c, :k], [:q]])
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot make the second group also have two variables instead of only :q

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the documentation example to use balanced groups with two variables each: [[:c, :k], [:y, :i]] instead of [[:c, :k], [:q]]. The output description now reflects a 4×4 matrix instead of 3×3. Commit: 093b24d

covar_var_idx = @ignore_derivatives parse_variables_input_to_index(covariance, 𝓂.timings)

# Check if covariance is grouped and parse groups
is_grouped_covar = @ignore_derivatives is_grouped_covariance_input(covariance)
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot why do you need this indicator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The indicator was used to check whether to extract grouped covariances or use standard extraction. Simplified this by directly checking !isnothing(covar_groups) instead of maintaining a separate is_grouped_covar variable. Commit: a13fd2d

Changed the grouped covariance example from [[:c, :k], [:q]] to [[:c, :k], [:y, :i]] to better demonstrate the feature with balanced groups.

Co-authored-by: thorek1 <[email protected]>
# Type definitions
const Symbol_input = Union{Symbol,Vector{Symbol},Matrix{Symbol},Tuple{Symbol,Vararg{Symbol}}}
const String_input = Union{String,Vector{String},Matrix{String},Tuple{String,Vararg{String}}}
const Symbol_input = Union{Symbol,Vector{Symbol},Matrix{Symbol},Tuple{Symbol,Vararg{Symbol}},Vector{Vector{Symbol}}}
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot also allow for vector of tuples or tuples of tuples

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added support for Vector{Tuple{Symbol,Vararg{Symbol}}} and Tuple{Tuple{Symbol,Vararg{Symbol}},Vararg{Tuple{Symbol,Vararg{Symbol}}}} (and their String equivalents). Updated Symbol_input and String_input type definitions, and modified is_grouped_covariance_input, parse_covariance_groups, and parse_variables_input_to_index to handle these formats. Commit: a13fd2d

Copilot AI requested a review from thorek1 November 6, 2025 09:53
Copilot finished work on behalf of thorek1 November 6, 2025 09:53
…iance, simplify indicator check

Extended Symbol_input and String_input to support Vector{Tuple} and Tuple{Tuple} formats for grouped covariance. Updated is_grouped_covariance_input, parse_covariance_groups, and parse_variables_input_to_index to handle these new formats. Simplified is_grouped_covar indicator by directly checking !isnothing(covar_groups) where needed.

Co-authored-by: thorek1 <[email protected]>
Copilot finished work on behalf of thorek1 November 6, 2025 10:01
for i in 1:
for j in 1:
for k in 1:
if sum(abs, s_s_s_to_y₃[:, col_idx]) > tol
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot implement the vectorised approach here and wherever else in the determine_efficient_order functions where you didnt do it yet

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applied vectorized approach to all remaining locations in determine_efficient_order functions:

  1. Second-order covariance section: Replaced nested loops with reshape to nˢ×nˢ matrix and dimensional sums
  2. Third-order covariance section: Replaced triple-nested loops with reshape to nˢ×nˢ×nˢ tensor and dimensional sums
  3. Propagation loops: Precomputed matrix slices and eliminated copy() calls in both second and third-order covariance sections

All nested loops for dependency detection have been removed. Commit: 291bf89

…efficient_order

Replaced all remaining nested loops in covariance variable dependency detection with vectorized reshape and sum operations. Also precomputed matrix slices and eliminated copy() calls in propagation loops for both second and third order covariance sections.

Co-authored-by: thorek1 <[email protected]>
Copilot AI requested a review from thorek1 November 6, 2025 10:24
Copilot finished work on behalf of thorek1 November 6, 2025 10:25
@thorek1 thorek1 marked this pull request as ready for review November 9, 2025 10:22
Copilot AI review requested due to automatic review settings November 9, 2025 10:22
@thorek1 thorek1 merged commit b0f94cd into copilot/improve-plotting-docs Nov 9, 2025
2 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces grouped covariance computation functionality and performance optimizations for dependency tracking in DSGE models. The grouped covariance feature allows users to compute covariances only within specified variable groups, with cross-group covariances set to zero, providing more efficient and targeted statistical analysis.

  • Adds support for grouped covariance computation via nested vector/tuple inputs to get_statistics
  • Optimizes determine_efficient_order functions by reducing redundant allocations and repeated indexing operations
  • Extends type definitions to support nested vector and tuple structures for grouped inputs

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
src/MacroModelling.jl Extends type definitions for grouped inputs, adds helper functions for parsing grouped covariance, and optimizes dependency tracking loops with precomputed indices and vectorized operations
src/get_functions.jl Updates documentation and implements grouped covariance computation logic in get_statistics
test/functionality_tests.jl Adds comprehensive tests for grouped covariance functionality with validation of block-diagonal structure

ret[:covariance] = covar_result
else
# Return matrix with Inf-filled diagonal and zeros elsewhere
covar_result = fill(Inf * sum(abs2,parameter_values), length(covar_var_idx), length(covar_var_idx))
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

When the model is not solved, the unsolved case fills the entire matrix with Inf values, but the comment suggests it should have 'zeros elsewhere'. This is inconsistent with the solved case which has zeros for cross-group covariances. The unsolved matrix should match the structure of the solved case with zeros for non-diagonal elements or cross-group elements.

Suggested change
covar_result = fill(Inf * sum(abs2,parameter_values), length(covar_var_idx), length(covar_var_idx))
covar_result = zeros(T, length(covar_var_idx), length(covar_var_idx))
for i in 1:length(covar_var_idx)
covar_result[i, i] = Inf * sum(abs2, parameter_values)
end

Copilot uses AI. Check for mistakes.
Comment on lines +3424 to +3430
# Fill in only the specified groups
for group in covar_groups
for (i_idx, i) in enumerate(group)
for (j_idx, j) in enumerate(group)
# Find position in covar_var_idx
i_pos = findfirst(==(i), covar_var_idx)
j_pos = findfirst(==(j), covar_var_idx)
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

The findfirst calls inside nested loops are inefficient. Consider precomputing a lookup dictionary mapping indices to positions in covar_var_idx before the loop, similar to the optimization pattern used in lines 2144-2149 of MacroModelling.jl.

Suggested change
# Fill in only the specified groups
for group in covar_groups
for (i_idx, i) in enumerate(group)
for (j_idx, j) in enumerate(group)
# Find position in covar_var_idx
i_pos = findfirst(==(i), covar_var_idx)
j_pos = findfirst(==(j), covar_var_idx)
# Precompute lookup dictionary for covar_var_idx
covar_var_idx_lookup = Dict{eltype(covar_var_idx), Int}()
for (idx, val) in enumerate(covar_var_idx)
covar_var_idx_lookup[val] = idx
end
# Fill in only the specified groups
for group in covar_groups
for (i_idx, i) in enumerate(group)
for (j_idx, j) in enumerate(group)
# Find position in covar_var_idx using lookup
i_pos = get(covar_var_idx_lookup, i, nothing)
j_pos = get(covar_var_idx_lookup, j, nothing)

Copilot uses AI. Check for mistakes.
# considering both first and second order propagation
while true
prev_dependencies = copy(dependencies_in_states)
prev_dependencies = dependencies_in_states
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

Assignment prev_dependencies = dependencies_in_states creates a reference to the same boolean vector, not a copy. When dependencies_in_states is mutated later (line 2242), prev_dependencies also changes, making the comparison at line 2239 potentially incorrect. This should be prev_dependencies = copy(dependencies_in_states) to match the pattern from the original code.

Suggested change
prev_dependencies = dependencies_in_states
prev_dependencies = copy(dependencies_in_states)

Copilot uses AI. Check for mistakes.

while true
prev_dependencies = copy(dependencies_in_states)
prev_dependencies = dependencies_in_states
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

Assignment prev_dependencies = dependencies_in_states creates a reference to the same boolean vector, not a copy. When dependencies_in_states is mutated later (line 2296), prev_dependencies also changes, making the comparison at line 2293 potentially incorrect. This should be prev_dependencies = copy(dependencies_in_states).

Suggested change
prev_dependencies = dependencies_in_states
prev_dependencies = copy(dependencies_in_states)

Copilot uses AI. Check for mistakes.
# considering first, second, and third order propagation
while true
prev_dependencies = copy(dependencies_in_states)
prev_dependencies = dependencies_in_states
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

Assignment prev_dependencies = dependencies_in_states creates a reference to the same boolean vector, not a copy. When dependencies_in_states is mutated later (line 2414), prev_dependencies also changes, making the comparison at line 2411 potentially incorrect. This should be prev_dependencies = copy(dependencies_in_states).

Suggested change
prev_dependencies = dependencies_in_states
prev_dependencies = copy(dependencies_in_states)

Copilot uses AI. Check for mistakes.

while true
prev_dependencies = copy(dependencies_in_states)
prev_dependencies = dependencies_in_states
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

Assignment prev_dependencies = dependencies_in_states creates a reference to the same boolean vector, not a copy. When dependencies_in_states is mutated later (line 2489), prev_dependencies also changes, making the comparison at line 2486 potentially incorrect. This should be prev_dependencies = copy(dependencies_in_states).

Suggested change
prev_dependencies = dependencies_in_states
prev_dependencies = copy(dependencies_in_states)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants