From c3777f75c0a315f9d09ce8a73f505357591b25e8 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 4 Jan 2026 07:23:37 -0500 Subject: [PATCH] Add Runic.jl code formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CI workflow using fredrikekre/runic-action@v1 - Format all source files with Runic.jl 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/FormatCheck.yml | 19 +++ docs/make.jl | 24 +-- src/LightweightStats.jl | 22 +-- test/regression_tests.jl | 242 ++++++++++++++++-------------- test/runtests.jl | 2 +- 5 files changed, 170 insertions(+), 139 deletions(-) create mode 100644 .github/workflows/FormatCheck.yml diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml new file mode 100644 index 0000000..6762c6f --- /dev/null +++ b/.github/workflows/FormatCheck.yml @@ -0,0 +1,19 @@ +name: format-check + +on: + push: + branches: + - 'master' + - 'main' + - 'release-' + tags: '*' + pull_request: + +jobs: + runic: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: fredrikekre/runic-action@v1 + with: + version: '1' diff --git a/docs/make.jl b/docs/make.jl index c9c2427..a1b1c19 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,24 +1,24 @@ using LightweightStats using Documenter -DocMeta.setdocmeta!(LightweightStats, :DocTestSetup, :(using LightweightStats); recursive=true) +DocMeta.setdocmeta!(LightweightStats, :DocTestSetup, :(using LightweightStats); recursive = true) makedocs(; - modules=[LightweightStats], - authors="ChrisRackauckas ", - sitename="LightweightStats.jl", - format=Documenter.HTML(; - canonical="https://SciML.github.io/LightweightStats.jl", - edit_link="main", - assets=String[], + modules = [LightweightStats], + authors = "ChrisRackauckas ", + sitename = "LightweightStats.jl", + format = Documenter.HTML(; + canonical = "https://SciML.github.io/LightweightStats.jl", + edit_link = "main", + assets = String[], ), - pages=[ + pages = [ "Home" => "index.md", "API Reference" => "api.md", ], ) deploydocs(; - repo="github.com/SciML/LightweightStats.jl", - devbranch="main", -) \ No newline at end of file + repo = "github.com/SciML/LightweightStats.jl", + devbranch = "main", +) diff --git a/src/LightweightStats.jl b/src/LightweightStats.jl index c7af987..f6a065e 100644 --- a/src/LightweightStats.jl +++ b/src/LightweightStats.jl @@ -256,7 +256,7 @@ For uncorrected variance: function var(A::AbstractArray; corrected::Bool = true, mean = nothing, dims = :) if dims === (:) n = length(A) - isempty(A) && return oftype(real(zero(eltype(A)))/1, NaN) + isempty(A) && return oftype(real(zero(eltype(A))) / 1, NaN) m = mean === nothing ? LightweightStats.mean(A) : mean s = sum(x -> abs2(x - m), A) return corrected ? s / (n - 1) : s / n @@ -304,7 +304,7 @@ julia> std(A; dims=1) """ function std(A::AbstractArray; corrected::Bool = true, mean = nothing, dims = :) return dims === (:) ? sqrt(var(A; corrected = corrected, mean = mean)) : - sqrt.(var(A; corrected = corrected, mean = mean, dims = dims)) + sqrt.(var(A; corrected = corrected, mean = mean, dims = dims)) end """ @@ -343,7 +343,7 @@ Throws `DimensionMismatch` if vectors have different lengths. function cov(x::AbstractVector, y::AbstractVector; corrected::Bool = true) n = length(x) length(y) == n || throw(DimensionMismatch("x and y must have the same length")) - n == 0 && return oftype(real(zero(eltype(x)))/1, NaN) + n == 0 && return oftype(real(zero(eltype(x))) / 1, NaN) xmean = mean(x) ymean = mean(y) @@ -423,11 +423,11 @@ Throws `ArgumentError` if dims is not 1 or 2. function cov(X::AbstractMatrix; dims::Int = 1, corrected::Bool = true) if dims == 1 n, p = size(X) - n == 0 && return fill(oftype(real(zero(eltype(X)))/1, NaN), p, p) + n == 0 && return fill(oftype(real(zero(eltype(X))) / 1, NaN), p, p) means = vec(mean(X; dims = 1)) C = zeros(float(real(eltype(X))), p, p) - + # Center the data once using broadcasting X_centered = X .- means' @@ -444,11 +444,11 @@ function cov(X::AbstractMatrix; dims::Int = 1, corrected::Bool = true) return C elseif dims == 2 n, p = size(X') - n == 0 && return fill(oftype(real(zero(eltype(X)))/1, NaN), p, p) + n == 0 && return fill(oftype(real(zero(eltype(X))) / 1, NaN), p, p) means = vec(mean(X; dims = 2)) C = zeros(float(real(eltype(X))), p, p) - + # Center the data once using broadcasting X_centered = X .- means @@ -515,7 +515,7 @@ function cor(x::AbstractVector, y::AbstractVector) sx = std(x; corrected = false) sy = std(y; corrected = false) - (sx == 0 || sy == 0) && return oftype(real(zero(eltype(x)))/1, NaN) + (sx == 0 || sy == 0) && return oftype(real(zero(eltype(x))) / 1, NaN) return cov(x, y; corrected = false) / (sx * sy) end @@ -569,11 +569,11 @@ function cor(X::AbstractMatrix; dims::Int = 1) # Use broadcasting to compute correlation matrix # Create outer product of standard deviations s_outer = s * s' - + # Handle zero variance cases with broadcasting R = similar(C) zero_mask = (s_outer .== 0) - R[zero_mask] .= oftype(real(zero(eltype(X)))/1, NaN) + R[zero_mask] .= oftype(real(zero(eltype(X))) / 1, NaN) R[.!zero_mask] = C[.!zero_mask] ./ s_outer[.!zero_mask] return R @@ -810,4 +810,4 @@ using PrecompileTools: @compile_workload cor(X) end -end \ No newline at end of file +end diff --git a/test/regression_tests.jl b/test/regression_tests.jl index a9697f3..897718b 100644 --- a/test/regression_tests.jl +++ b/test/regression_tests.jl @@ -6,7 +6,7 @@ using Random @testset "Regression tests against Statistics.jl" begin # Set seed for reproducibility Random.seed!(12345) - + # Test data of various types and sizes test_vectors = [ [1, 2, 3, 4, 5], @@ -18,7 +18,7 @@ using Random collect(1:1000), [1], # single element ] - + test_matrices = [ [1 2 3; 4 5 6], randn(10, 5), @@ -27,52 +27,52 @@ using Random ones(3, 3), [1;;], # 1x1 matrix ] - + @testset "mean - vectors" begin for v in test_vectors - @test LightweightStats.mean(v) ≈ Statistics.mean(v) rtol=1e-8 + @test LightweightStats.mean(v) ≈ Statistics.mean(v) rtol = 1.0e-8 # Test with function argument - @test LightweightStats.mean(x -> x^2, v) ≈ Statistics.mean(x -> x^2, v) rtol=1e-8 - @test LightweightStats.mean(abs, v) ≈ Statistics.mean(abs, v) rtol=1e-8 + @test LightweightStats.mean(x -> x^2, v) ≈ Statistics.mean(x -> x^2, v) rtol = 1.0e-8 + @test LightweightStats.mean(abs, v) ≈ Statistics.mean(abs, v) rtol = 1.0e-8 end - + # Test empty array error @test_throws ArgumentError LightweightStats.mean(Int[]) # Note: Statistics.mean returns NaN for empty arrays, not an error @test isnan(Statistics.mean(Float64[])) end - + @testset "mean - matrices with dims" begin for m in test_matrices - @test LightweightStats.mean(m) ≈ Statistics.mean(m) rtol=1e-8 - @test LightweightStats.mean(m; dims=1) ≈ Statistics.mean(m; dims=1) rtol=1e-8 - @test LightweightStats.mean(m; dims=2) ≈ Statistics.mean(m; dims=2) rtol=1e-8 + @test LightweightStats.mean(m) ≈ Statistics.mean(m) rtol = 1.0e-8 + @test LightweightStats.mean(m; dims = 1) ≈ Statistics.mean(m; dims = 1) rtol = 1.0e-8 + @test LightweightStats.mean(m; dims = 2) ≈ Statistics.mean(m; dims = 2) rtol = 1.0e-8 end end - + @testset "median - vectors" begin for v in test_vectors # Compare values, not types (Statistics.jl may return different types) - @test LightweightStats.median(v) ≈ Statistics.median(v) rtol=1e-8 + @test LightweightStats.median(v) ≈ Statistics.median(v) rtol = 1.0e-8 end - + # Test odd and even length vectors @test LightweightStats.median([1, 2, 3]) == Statistics.median([1, 2, 3]) @test LightweightStats.median([1, 2, 3, 4]) == Statistics.median([1, 2, 3, 4]) - + # Test empty array error @test_throws ArgumentError LightweightStats.median(Int[]) @test_throws ArgumentError Statistics.median(Int[]) end - + @testset "median - matrices with dims" begin for m in test_matrices - @test LightweightStats.median(m) ≈ Statistics.median(m) rtol=1e-8 - @test LightweightStats.median(m; dims=1) ≈ Statistics.median(m; dims=1) rtol=1e-8 - @test LightweightStats.median(m; dims=2) ≈ Statistics.median(m; dims=2) rtol=1e-8 + @test LightweightStats.median(m) ≈ Statistics.median(m) rtol = 1.0e-8 + @test LightweightStats.median(m; dims = 1) ≈ Statistics.median(m; dims = 1) rtol = 1.0e-8 + @test LightweightStats.median(m; dims = 2) ≈ Statistics.median(m; dims = 2) rtol = 1.0e-8 end end - + @testset "var - variance" begin for v in test_vectors lw_var = LightweightStats.var(v) @@ -81,28 +81,28 @@ using Random if isnan(lw_var) && isnan(st_var) @test true # Both are NaN, which is correct else - @test lw_var ≈ st_var rtol=1e-8 + @test lw_var ≈ st_var rtol = 1.0e-8 end - + # Test corrected parameter - @test LightweightStats.var(v; corrected=false) ≈ Statistics.var(v; corrected=false) rtol=1e-8 - + @test LightweightStats.var(v; corrected = false) ≈ Statistics.var(v; corrected = false) rtol = 1.0e-8 + # With known mean m = Statistics.mean(v) - lw_var_m = LightweightStats.var(v; mean=m) - st_var_m = Statistics.var(v; mean=m) + lw_var_m = LightweightStats.var(v; mean = m) + st_var_m = Statistics.var(v; mean = m) if isnan(lw_var_m) && isnan(st_var_m) @test true else - @test lw_var_m ≈ st_var_m rtol=1e-8 + @test lw_var_m ≈ st_var_m rtol = 1.0e-8 end end - + # Test empty array returns NaN @test isnan(LightweightStats.var(Float64[])) @test isnan(Statistics.var(Float64[])) end - + @testset "var - matrices with dims" begin for m in test_matrices # Compare overall variance @@ -111,20 +111,22 @@ using Random if isnan(lw_var) && isnan(st_var) @test true else - @test lw_var ≈ st_var rtol=1e-8 + @test lw_var ≈ st_var rtol = 1.0e-8 end - + # Compare along dimensions, handling NaN arrays for dims in [1, 2] - lw_var_dims = LightweightStats.var(m; dims=dims) - st_var_dims = Statistics.var(m; dims=dims) + lw_var_dims = LightweightStats.var(m; dims = dims) + st_var_dims = Statistics.var(m; dims = dims) # Element-wise comparison handling NaN - @test all(i -> (isnan(lw_var_dims[i]) && isnan(st_var_dims[i])) || - (lw_var_dims[i] ≈ st_var_dims[i]), eachindex(lw_var_dims)) + @test all( + i -> (isnan(lw_var_dims[i]) && isnan(st_var_dims[i])) || + (lw_var_dims[i] ≈ st_var_dims[i]), eachindex(lw_var_dims) + ) end end end - + @testset "std - standard deviation" begin for v in test_vectors lw_std = LightweightStats.std(v) @@ -132,21 +134,21 @@ using Random if isnan(lw_std) && isnan(st_std) @test true else - @test lw_std ≈ st_std rtol=1e-8 + @test lw_std ≈ st_std rtol = 1.0e-8 end - + # With known mean m = Statistics.mean(v) - lw_std_m = LightweightStats.std(v; mean=m) - st_std_m = Statistics.std(v; mean=m) + lw_std_m = LightweightStats.std(v; mean = m) + st_std_m = Statistics.std(v; mean = m) if isnan(lw_std_m) && isnan(st_std_m) @test true else - @test lw_std_m ≈ st_std_m rtol=1e-8 + @test lw_std_m ≈ st_std_m rtol = 1.0e-8 end end end - + @testset "std - matrices with dims" begin for m in test_matrices lw_std = LightweightStats.std(m) @@ -154,109 +156,119 @@ using Random if isnan(lw_std) && isnan(st_std) @test true else - @test lw_std ≈ st_std rtol=1e-8 + @test lw_std ≈ st_std rtol = 1.0e-8 end - + for dims in [1, 2] - lw_std_dims = LightweightStats.std(m; dims=dims) - st_std_dims = Statistics.std(m; dims=dims) - @test all(i -> (isnan(lw_std_dims[i]) && isnan(st_std_dims[i])) || - (lw_std_dims[i] ≈ st_std_dims[i]), eachindex(lw_std_dims)) + lw_std_dims = LightweightStats.std(m; dims = dims) + st_std_dims = Statistics.std(m; dims = dims) + @test all( + i -> (isnan(lw_std_dims[i]) && isnan(st_std_dims[i])) || + (lw_std_dims[i] ≈ st_std_dims[i]), eachindex(lw_std_dims) + ) end end end - + @testset "cov - covariance vectors" begin x = randn(20) y = randn(20) - - @test LightweightStats.cov(x, y) ≈ Statistics.cov(x, y) rtol=1e-8 - @test LightweightStats.cov(x, y; corrected=true) ≈ Statistics.cov(x, y; corrected=true) rtol=1e-8 - @test LightweightStats.cov(x, y; corrected=false) ≈ Statistics.cov(x, y; corrected=false) rtol=1e-8 - + + @test LightweightStats.cov(x, y) ≈ Statistics.cov(x, y) rtol = 1.0e-8 + @test LightweightStats.cov(x, y; corrected = true) ≈ Statistics.cov(x, y; corrected = true) rtol = 1.0e-8 + @test LightweightStats.cov(x, y; corrected = false) ≈ Statistics.cov(x, y; corrected = false) rtol = 1.0e-8 + # Self-covariance equals variance - @test LightweightStats.cov(x) ≈ Statistics.cov(x) rtol=1e-8 - @test LightweightStats.cov(x) ≈ LightweightStats.var(x) rtol=1e-8 - + @test LightweightStats.cov(x) ≈ Statistics.cov(x) rtol = 1.0e-8 + @test LightweightStats.cov(x) ≈ LightweightStats.var(x) rtol = 1.0e-8 + # Test dimension mismatch @test_throws DimensionMismatch LightweightStats.cov(x[1:10], y) @test_throws DimensionMismatch Statistics.cov(x[1:10], y) end - + @testset "cov - covariance matrices" begin for m in test_matrices[1:4] # Skip single element cases - C_lw = LightweightStats.cov(m; dims=1) - C_st = Statistics.cov(m; dims=1) + C_lw = LightweightStats.cov(m; dims = 1) + C_st = Statistics.cov(m; dims = 1) # Element-wise comparison handling potential NaN - @test all(i -> (isnan(C_lw[i]) && isnan(C_st[i])) || - (C_lw[i] ≈ C_st[i]), eachindex(C_lw)) + @test all( + i -> (isnan(C_lw[i]) && isnan(C_st[i])) || + (C_lw[i] ≈ C_st[i]), eachindex(C_lw) + ) @test size(C_lw) == size(C_st) - - C_lw = LightweightStats.cov(m; dims=2) - C_st = Statistics.cov(m; dims=2) - @test all(i -> (isnan(C_lw[i]) && isnan(C_st[i])) || - (C_lw[i] ≈ C_st[i]), eachindex(C_lw)) + + C_lw = LightweightStats.cov(m; dims = 2) + C_st = Statistics.cov(m; dims = 2) + @test all( + i -> (isnan(C_lw[i]) && isnan(C_st[i])) || + (C_lw[i] ≈ C_st[i]), eachindex(C_lw) + ) @test size(C_lw) == size(C_st) end end - + @testset "cor - correlation vectors" begin x = randn(30) y = randn(30) - - @test LightweightStats.cor(x, y) ≈ Statistics.cor(x, y) rtol=1e-8 - @test LightweightStats.cor(x, x) ≈ 1.0 rtol=1e-8 - @test Statistics.cor(x, x) ≈ 1.0 rtol=1e-8 - + + @test LightweightStats.cor(x, y) ≈ Statistics.cor(x, y) rtol = 1.0e-8 + @test LightweightStats.cor(x, x) ≈ 1.0 rtol = 1.0e-8 + @test Statistics.cor(x, x) ≈ 1.0 rtol = 1.0e-8 + # Perfect positive and negative correlation z = 2 * x .+ 3 - @test LightweightStats.cor(x, z) ≈ Statistics.cor(x, z) rtol=1e-8 - @test LightweightStats.cor(x, z) ≈ 1.0 rtol=1e-8 - + @test LightweightStats.cor(x, z) ≈ Statistics.cor(x, z) rtol = 1.0e-8 + @test LightweightStats.cor(x, z) ≈ 1.0 rtol = 1.0e-8 + w = -2 * x .+ 5 - @test LightweightStats.cor(x, w) ≈ Statistics.cor(x, w) rtol=1e-8 - @test LightweightStats.cor(x, w) ≈ -1.0 rtol=1e-8 - + @test LightweightStats.cor(x, w) ≈ Statistics.cor(x, w) rtol = 1.0e-8 + @test LightweightStats.cor(x, w) ≈ -1.0 rtol = 1.0e-8 + # Test zero variance case constant = ones(10) @test isnan(LightweightStats.cor(constant, randn(10))) @test isnan(Statistics.cor(constant, randn(10))) end - + @testset "cor - correlation matrices" begin for m in test_matrices[1:4] - R_lw = LightweightStats.cor(m; dims=1) - R_st = Statistics.cor(m; dims=1) - @test all(i -> (isnan(R_lw[i]) && isnan(R_st[i])) || - (R_lw[i] ≈ R_st[i]), eachindex(R_lw)) + R_lw = LightweightStats.cor(m; dims = 1) + R_st = Statistics.cor(m; dims = 1) + @test all( + i -> (isnan(R_lw[i]) && isnan(R_st[i])) || + (R_lw[i] ≈ R_st[i]), eachindex(R_lw) + ) @test size(R_lw) == size(R_st) - + # Check diagonal elements are 1 (or NaN for zero variance) for i in 1:size(R_lw, 1) if !isnan(R_lw[i, i]) - @test R_lw[i, i] ≈ 1.0 rtol=1e-6 + @test R_lw[i, i] ≈ 1.0 rtol = 1.0e-6 end end - - R_lw = LightweightStats.cor(m; dims=2) - R_st = Statistics.cor(m; dims=2) - @test all(i -> (isnan(R_lw[i]) && isnan(R_st[i])) || - (R_lw[i] ≈ R_st[i]), eachindex(R_lw)) + + R_lw = LightweightStats.cor(m; dims = 2) + R_st = Statistics.cor(m; dims = 2) + @test all( + i -> (isnan(R_lw[i]) && isnan(R_st[i])) || + (R_lw[i] ≈ R_st[i]), eachindex(R_lw) + ) end end - + @testset "quantile - single quantile" begin for v in test_vectors for p in [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0] - @test LightweightStats.quantile(v, p) ≈ Statistics.quantile(v, p) rtol=1e-8 + @test LightweightStats.quantile(v, p) ≈ Statistics.quantile(v, p) rtol = 1.0e-8 end end - + # Test edge cases v = [1, 2, 3, 4, 5] @test LightweightStats.quantile(v, 0.0) == Statistics.quantile(v, 0.0) @test LightweightStats.quantile(v, 1.0) == Statistics.quantile(v, 1.0) - + # Test errors @test_throws ArgumentError LightweightStats.quantile(v, -0.1) @test_throws ArgumentError LightweightStats.quantile(v, 1.1) @@ -265,38 +277,38 @@ using Random @test_throws ArgumentError Statistics.quantile(v, 1.1) @test_throws ArgumentError Statistics.quantile([], 0.5) end - + @testset "quantile - multiple quantiles" begin for v in test_vectors ps = [0.25, 0.5, 0.75] - @test LightweightStats.quantile(v, ps) ≈ Statistics.quantile(v, ps) rtol=1e-8 - + @test LightweightStats.quantile(v, ps) ≈ Statistics.quantile(v, ps) rtol = 1.0e-8 + ps = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] - @test LightweightStats.quantile(v, ps) ≈ Statistics.quantile(v, ps) rtol=1e-8 + @test LightweightStats.quantile(v, ps) ≈ Statistics.quantile(v, ps) rtol = 1.0e-8 end end - + @testset "middle" begin for v in test_vectors - @test LightweightStats.middle(v) ≈ Statistics.middle(v) rtol=1e-8 + @test LightweightStats.middle(v) ≈ Statistics.middle(v) rtol = 1.0e-8 end - + # Test with explicit values @test LightweightStats.middle(1, 5) == Statistics.middle(1, 5) @test LightweightStats.middle(1.0, 5.0) == Statistics.middle(1.0, 5.0) @test LightweightStats.middle(-10, 10) == Statistics.middle(-10, 10) - + # Test single value @test LightweightStats.middle(42) == Statistics.middle(42) @test LightweightStats.middle(3.14) == Statistics.middle(3.14) - + # Test error for empty @test_throws ArgumentError LightweightStats.middle([]) # Note: Statistics.jl may throw different error types on different platforms/versions # so we just verify it throws some error @test_throws Exception Statistics.middle([]) end - + @testset "Edge cases and special values" begin # NaN handling v_nan = [1.0, 2.0, NaN, 4.0, 5.0] @@ -304,20 +316,20 @@ using Random @test isnan(Statistics.mean(v_nan)) @test isnan(LightweightStats.std(v_nan)) @test isnan(Statistics.std(v_nan)) - + # Inf handling v_inf = [1.0, 2.0, Inf, 4.0, 5.0] @test LightweightStats.mean(v_inf) == Statistics.mean(v_inf) @test isinf(LightweightStats.mean(v_inf)) - + # Very large numbers - v_large = [1e307, 2e307, 3e307] - @test LightweightStats.mean(v_large) ≈ Statistics.mean(v_large) rtol=1e-8 - @test LightweightStats.std(v_large) ≈ Statistics.std(v_large) rtol=1e-8 - + v_large = [1.0e307, 2.0e307, 3.0e307] + @test LightweightStats.mean(v_large) ≈ Statistics.mean(v_large) rtol = 1.0e-8 + @test LightweightStats.std(v_large) ≈ Statistics.std(v_large) rtol = 1.0e-8 + # Very small numbers - v_small = [1e-307, 2e-307, 3e-307] - @test LightweightStats.mean(v_small) ≈ Statistics.mean(v_small) rtol=1e-8 - @test LightweightStats.std(v_small) ≈ Statistics.std(v_small) rtol=1e-8 + v_small = [1.0e-307, 2.0e-307, 3.0e-307] + @test LightweightStats.mean(v_small) ≈ Statistics.mean(v_small) rtol = 1.0e-8 + @test LightweightStats.std(v_small) ≈ Statistics.std(v_small) rtol = 1.0e-8 end -end \ No newline at end of file +end diff --git a/test/runtests.jl b/test/runtests.jl index 17e33e5..fda0661 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,7 +19,7 @@ using LightweightStats: mean, median, std, var, cov, cor, quantile, middle @test mean([1.5, 2.5, 3.5]) ≈ 2.5 @test mean(Float32[1, 2, 3]) ≈ 2.0f0 - @test mean(x -> x^2, [1, 2, 3]) ≈ 14/3 + @test mean(x -> x^2, [1, 2, 3]) ≈ 14 / 3 A = [1 2 3; 4 5 6] @test mean(A) ≈ 3.5