MinimumVarianceAnalysis
A Julia package for minimum or maximum variance analysis (MVA).
MinimumVarianceAnalysis — Module
Estimate the direction normal $𝐧̂$ to a one-dimensional structure using minimum or maximum variance analysis (MVA).
- [x] Maximum Variance Analysis on Magnetic Field (MVAB)
- [x] Maximum Variance Analysis on Electric Field (MVAE)
API Reference
MinimumVarianceAnalysis.MinimumVarianceAnalysis — Module
Estimate the direction normal $𝐧̂$ to a one-dimensional structure using minimum or maximum variance analysis (MVA).
MinimumVarianceAnalysis.B_x3_error — Method
Calculate the composite statistical error estimate for ⟨B·x₃⟩: |Δ⟨B·x₃⟩| = √(λ₃/(M-1) + (Δφ₃₂⟨B⟩·x₂)² + (Δφ₃₁⟨B⟩·x₁)²)
Parameters:
- λ₁, λ₂, λ₃: eigenvalues in descending order
- M: number of samples
- B: mean magnetic field vector
- x₁, x₂, x₃: eigenvectors
MinimumVarianceAnalysis.E_x1_error — Method
E_x1_error(λ₁, λ₂, λ₃, M, E, x₁, x₂, x₃)Calculate the composite statistical error estimate for ⟨E·x₁⟩ (the mean electric field along the maximum variance / normal direction):
$|Δ⟨\mathbf{E}·\mathbf{x}_1⟩| = \sqrt{\frac{λ_1}{M-1} + (Δφ_{12}⟨\mathbf{E}⟩·\mathbf{x}_2)^2 + (Δφ_{13}⟨\mathbf{E}⟩·\mathbf{x}_3)^2}$
Parameters:
- λ₁, λ₂, λ₃: eigenvalues in descending order
- M: number of samples
- E: mean electric field vector
- x₁, x₂, x₃: eigenvectors
MinimumVarianceAnalysis.check_mva_eigen — Method
check_mva_eigen(F; r0=5, verbose=false, field = :B)Check the quality of the MVA result.
If λ₁ ≥ λ₂ ≥ λ₃ are 3 eigenvalues of the constructed matrix M. For MVAB, a good indicator of nice results should have $|λ₂ / λ₃| > r₀$ (default $r₀ = 5$).
For MVAE, a reliable normal direction requires the maximum eigenvalue $λ₁$ to be well-separated from the intermediate eigenvalue $λ₂$. The ratio $|λ₁ / λ₂| > r₀$ is used as a quality indicator.
MinimumVarianceAnalysis.convection_efield — Method
convection_efield(v, B; dim=nothing)Compute the convection electric field $\mathbf{E} = -\mathbf{v} × \mathbf{B}$ from plasma velocity v and magnetic field B.
This can be used as a proxy for the measured electric field when direct measurements are unavailable.
MinimumVarianceAnalysis.mva — Function
mva(V, F=V; dim=nothing, kwargs...)Transform a timeseries V into the LMN coordinate system based on the minimum/maximum variance analysis of reference field F along the dim dimension (time).
MinimumVarianceAnalysis.mva_eigen — Method
mva_eigen(x::AbstractMatrix; dim = nothing, sort=(;), check=false) -> F::EigenPerform minimum variance analysis of the magnetic field B or maximum variance analysis of the electric field E when field=:E.
x varies along the dim dimension.
Return Eigen factorization object F which contains the eigenvalues in F.values and the eigenvectors in the columns of the matrix F.vectors. The kth eigenvector can be obtained from the slice F.vectors[:, k].
Set check=true to check the reliability of the result.
Notes
For a one-dimensional current layer, the tangential electric field components are approximately constant across the boundary, while the normal component exhibits the largest variation. Therefore, the eigenvector corresponding to the maximum eigenvalue $λ_1$ (first column of F.vectors) gives an estimate of the boundary normal direction.
MinimumVarianceAnalysis.normal — Method
normal(F::Eigen; field=:B)Return the boundary normal eigenvector from an MVA result.
field=:B(MVAB): minimum variance direction (last eigenvector)field=:E(MVAE): maximum variance direction (first eigenvector)
MinimumVarianceAnalysis.transform — Method
transform(A, mat::AbstractMatrix; dim=nothing, query=nothing)Transform A into a new coordinate system using transformation matrix mat along the dim dimension (time).
MinimumVarianceAnalysis.Δφij — Method
Δφij(λᵢ, λⱼ, λ₃, M)Calculate the phase error between components i and j according to: |Δφᵢⱼ| = |Δφⱼᵢ| = √(λ₃/(M-1) * (λᵢ + λⱼ - λ₃)/(λᵢ - λⱼ)²)
Parameters:
- λᵢ: eigenvalue i
- λⱼ: eigenvalue j
- λ₃: smallest eigenvalue (λ₃)
- M: number of samples
Error estimates for MVA:
MinimumVarianceAnalysis.Δφij — Function
Δφij(λᵢ, λⱼ, λ₃, M)Calculate the phase error between components i and j according to: |Δφᵢⱼ| = |Δφⱼᵢ| = √(λ₃/(M-1) * (λᵢ + λⱼ - λ₃)/(λᵢ - λⱼ)²)
Parameters:
- λᵢ: eigenvalue i
- λⱼ: eigenvalue j
- λ₃: smallest eigenvalue (λ₃)
- M: number of samples
MinimumVarianceAnalysis.B_x3_error — Function
Calculate the composite statistical error estimate for ⟨B·x₃⟩: |Δ⟨B·x₃⟩| = √(λ₃/(M-1) + (Δφ₃₂⟨B⟩·x₂)² + (Δφ₃₁⟨B⟩·x₁)²)
Parameters:
- λ₁, λ₂, λ₃: eigenvalues in descending order
- M: number of samples
- B: mean magnetic field vector
- x₁, x₂, x₃: eigenvectors
MinimumVarianceAnalysis.E_x1_error — Function
E_x1_error(λ₁, λ₂, λ₃, M, E, x₁, x₂, x₃)Calculate the composite statistical error estimate for ⟨E·x₁⟩ (the mean electric field along the maximum variance / normal direction):
$|Δ⟨\mathbf{E}·\mathbf{x}_1⟩| = \sqrt{\frac{λ_1}{M-1} + (Δφ_{12}⟨\mathbf{E}⟩·\mathbf{x}_2)^2 + (Δφ_{13}⟨\mathbf{E}⟩·\mathbf{x}_3)^2}$
Parameters:
- λ₁, λ₂, λ₃: eigenvalues in descending order
- M: number of samples
- E: mean electric field vector
- x₁, x₂, x₃: eigenvectors
Validation with PySPEDAS
References: mva_eigen, test_minvar.py - PySPEDAS
using MinimumVarianceAnalysis
using PySPEDAS
using PySPEDAS.PythonCall
@py import pyspedas.cotrans_tools.tests.test_minvar: TestMinvar
@py import pyspedas.cotrans_tools.minvar_matrix_make: minvar_matrix_make
isapprox_eigenvector(v1, v2) = isapprox(v1, v2) || isapprox(v1, -v2)
pytest = TestMinvar()
pytest.setUpClass()
thb_fgs_gsm = get_data("idl_thb_fgs_gsm_mvaclipped1")
jl_mva_eigen = mva_eigen(thb_fgs_gsm)
jl_mva_mat = jl_mva_eigen.vectors
jl_mva_vals = jl_mva_eigen.values
py_mva_vals = PyArray(pytest.vals.y) |> vec
py_mva_mat = PyArray(pytest.mat.y[0])'
@assert isapprox(jl_mva_vals, py_mva_vals)
@assert all(isapprox_eigenvector.(eachcol(jl_mva_mat), eachcol(py_mva_mat))) CondaPkg Found dependencies: /home/runner/work/MinimumVarianceAnalysis.jl/MinimumVarianceAnalysis.jl/docs/CondaPkg.toml
CondaPkg Found dependencies: /home/runner/.julia/packages/PythonCall/83z4q/CondaPkg.toml
CondaPkg Found dependencies: /home/runner/.julia/packages/PySPEDAS/wAEow/CondaPkg.toml
CondaPkg Found dependencies: /home/runner/.julia/packages/CondaPkg/0UqYV/CondaPkg.toml
CondaPkg Resolving changes
+ libstdcxx
+ libstdcxx-ng
+ netcdf4
+ openssl
+ pyspedas (pip)
+ python
+ uv
CondaPkg Initialising pixi
│ /home/runner/.julia/artifacts/cefba4912c2b400756d043a2563ef77a0088866b/bin/pixi
│ init
│ --format pixi
└ /home/runner/work/MinimumVarianceAnalysis.jl/MinimumVarianceAnalysis.jl/docs/.CondaPkg
✔ Created /home/runner/work/MinimumVarianceAnalysis.jl/MinimumVarianceAnalysis.jl/docs/.CondaPkg/pixi.toml
CondaPkg Wrote /home/runner/work/MinimumVarianceAnalysis.jl/MinimumVarianceAnalysis.jl/docs/.CondaPkg/pixi.toml
│ [dependencies]
│ netcdf4 = "*"
│ libstdcxx = ">=3.4,<15.0"
│ openssl = ">=3, <3.6"
│ libstdcxx-ng = ">=3.4,<15.0"
│ uv = ">=0.4"
│
│ [dependencies.python]
│ channel = "conda-forge"
│ build = "*cp*"
│ version = "<3.14, >=3.10,!=3.14.0,!=3.14.1,<4"
│
│ [project]
│ name = ".CondaPkg"
│ platforms = ["linux-64"]
│ channels = ["conda-forge"]
│ channel-priority = "strict"
│ description = "automatically generated by CondaPkg.jl"
│
│ [pypi-dependencies.pyspedas]
└ git = "https://github.com/spedas/pyspedas"
CondaPkg Installing packages
│ /home/runner/.julia/artifacts/cefba4912c2b400756d043a2563ef77a0088866b/bin/pixi
│ install
└ --manifest-path /home/runner/work/MinimumVarianceAnalysis.jl/MinimumVarianceAnalysis.jl/docs/.CondaPkg/pixi.toml
✔ The default environment has been installed.
13-Feb-26 19:40:39: Downloading https://github.com/spedas/test_data/raw/refs/heads/main/cotrans_tools/mva_python_validate.tplot to _testing_output/cotrans_tools/cotrans_tools/mva_python_validate.tplot
13-Feb-26 19:40:39: Download of _testing_output/cotrans_tools/cotrans_tools/mva_python_validate.tplot complete, 2.282 MB in 0.5 sec (4.306 MB/sec) (transfer_normal)
13-Feb-26 19:40:39: del_data: No valid tplot variables found, returning
13-Feb-26 19:40:39: store_data: Data array for variable thb_fgs_gsm_mvaclipped1_mva_mat has 3 dimensions, but only 1 v_n keys plus time. Adding empty v_n keys.Since eigenvectors are only unique up to sign; therefore, the test checks if each Julia eigenvector is approximately equal to the corresponding Python eigenvector or its negative.
Benchmark
using Chairmarks
@b mva_eigen(thb_fgs_gsm), minvar_matrix_make("idl_thb_fgs_gsm_mvaclipped1")(1.754 μs (2 allocs: 272 bytes), 2.090 ms (2 allocs: 32 bytes))Julia demonstrates a performance advantage of approximately 1000 times over Python, with significantly reduced memory allocations. Moreover, Julia's implementation is generalized for N-dimensional data.