From e7cb4098b28acfb3133ea2527b61e0ce98361fbe Mon Sep 17 00:00:00 2001 From: Mikhail Mikhasenko Date: Sat, 12 Apr 2025 16:56:52 +0200 Subject: [PATCH 1/2] implemented update and tests --- src/ComponentArrays.jl | 1 + src/componentarray.jl | 45 +++++++++++++++++++++++++++++++++++ test/runtests.jl | 4 ++++ test/update_tests.jl | 54 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 test/update_tests.jl diff --git a/src/ComponentArrays.jl b/src/ComponentArrays.jl index ed38af8e..d5bed5f8 100644 --- a/src/ComponentArrays.jl +++ b/src/ComponentArrays.jl @@ -21,6 +21,7 @@ export AbstractAxis, Axis, PartitionedAxis, ShapedAxis, Shaped1DAxis, ViewAxis, include("componentarray.jl") export ComponentArray, ComponentVector, ComponentMatrix, getaxes, getdata, valkeys +export update_component_array include("componentindex.jl") export KeepIndex diff --git a/src/componentarray.jl b/src/componentarray.jl index c76be2a7..e3904893 100644 --- a/src/componentarray.jl +++ b/src/componentarray.jl @@ -360,3 +360,48 @@ julia> sum(prod(ca[k]) for k in valkeys(ca)) return :($k) end valkeys(ca::ComponentVector) = valkeys(getaxes(ca)[1]) + +""" + update_component_array(default::ComponentArray{T}, update::ComponentArray) where {T} + +Update a ComponentArray with values from another ComponentArray while preserving structure and type stability. +Only matching entries are updated, allowing for partial updates of nested structures. + +# Arguments +- `default::ComponentArray{T}`: The ComponentArray to update, serving as both template and target +- `update::ComponentArray`: The ComponentArray containing the new values + +# Returns +- A new ComponentArray with the same type and structure as `default`, updated with values from `update` + +# Example +```julia +default = ComponentArray(sig = (mu = 1.0, sigma = 2.0), bg = 3.0) +update = ComponentArray(sig = (mu = 1.1,), bg = 3.3) +result = update_component_array(default, update) +# result.sig.mu == 1.1 +# result.sig.sigma == 2.0 +# result.bg == 3.3 +``` +""" +function update_component_array(default::ComponentArray{T}, update::ComponentArray) where {T} + result = copy(default) + + # Update matching fields using ComponentArray's natural indexing + for key in propertynames(update) + if hasproperty(default, key) + update_val = update[key] + default_val = default[key] + + if default_val isa ComponentArray && update_val isa ComponentArray + # Recursively update nested ComponentArrays + result[key] = update_component_array(default_val, update_val) + else + # Direct update for leaf values + result[key] = update_val + end + end + end + + return result +end diff --git a/test/runtests.jl b/test/runtests.jl index 66d8dadb..1d4dd727 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -942,3 +942,7 @@ end @testset "Reactant" begin include("reactant_tests.jl") end + +@testset "ComponentArrays" begin + include("update_tests.jl") +end diff --git a/test/update_tests.jl b/test/update_tests.jl new file mode 100644 index 00000000..5bace3a3 --- /dev/null +++ b/test/update_tests.jl @@ -0,0 +1,54 @@ +using ComponentArrays +using Test + +@testset "ComponentArray Update" begin + # Test 1: Basic update + default = ComponentArray(sig = (mu = 1.0, sigma = 2.0), bg = 3.0) + update = ComponentArray(sig = (mu = 1.1,), bg = 3.3) + + result = update_component_array(default, update) + + @test result.sig.mu ≈ 1.1 + @test result.sig.sigma ≈ 2.0 + @test result.bg ≈ 3.3 + + # Test 2: Structure preservation + @test typeof(result) == typeof(default) + @test getfield(result, :axes) == getfield(default, :axes) + + # Test 3: Deep nested update + deep_default = ComponentArray( + outer = ( + inner = (x = 1.0, y = 2.0), + other = 3.0, + ), + top = 4.0, + ) + + deep_update = ComponentArray( + outer = ( + inner = (x = 10.0,), + ), + ) + + deep_result = update_component_array(deep_default, deep_update) + + @test deep_result.outer.inner.x ≈ 10.0 + @test deep_result.outer.inner.y ≈ 2.0 + @test deep_result.outer.other ≈ 3.0 + @test deep_result.top ≈ 4.0 + + # Test 4: Non-existing field update + nonexist_update = ComponentArray( + sig = (mu = 1.1,), + nonexistent = 42.0, # This field doesn't exist in default + ) + result_nonexist = update_component_array(default, nonexist_update) + + # Should preserve structure and only update existing fields + @test result_nonexist.sig.mu ≈ 1.1 + @test result_nonexist.sig.sigma ≈ 2.0 + @test !hasproperty(result_nonexist, :nonexistent) + @test typeof(result_nonexist) == typeof(default) + @test getfield(result_nonexist, :axes) == getfield(default, :axes) +end From 9c3bca17362daccdfed7ff9d3f5687662adae13a Mon Sep 17 00:00:00 2001 From: Mikhail Mikhasenko Date: Sat, 12 Apr 2025 17:10:56 +0200 Subject: [PATCH 2/2] using public getaxes method --- test/update_tests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/update_tests.jl b/test/update_tests.jl index 5bace3a3..84c05549 100644 --- a/test/update_tests.jl +++ b/test/update_tests.jl @@ -14,7 +14,7 @@ using Test # Test 2: Structure preservation @test typeof(result) == typeof(default) - @test getfield(result, :axes) == getfield(default, :axes) + @test getaxes(result) == getaxes(default) # Test 3: Deep nested update deep_default = ComponentArray( @@ -50,5 +50,5 @@ using Test @test result_nonexist.sig.sigma ≈ 2.0 @test !hasproperty(result_nonexist, :nonexistent) @test typeof(result_nonexist) == typeof(default) - @test getfield(result_nonexist, :axes) == getfield(default, :axes) + @test getaxes(result_nonexist) == getaxes(default) end