MinimumVarianceAnalysis

DOI version

A Julia package for 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.B_x3_errorMethod

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
source
MinimumVarianceAnalysis.E_x1_errorMethod
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
source
MinimumVarianceAnalysis.check_mva_eigenMethod
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.

source
MinimumVarianceAnalysis.convection_efieldMethod
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.

source
MinimumVarianceAnalysis.mvaFunction
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).

See also: mva_eigen, transform

source
MinimumVarianceAnalysis.mva_eigenMethod
mva_eigen(x::AbstractMatrix; dim = nothing, sort=(;), check=false) -> F::Eigen

Perform 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.

source
MinimumVarianceAnalysis.normalMethod
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)
source
MinimumVarianceAnalysis.transformMethod
transform(A, mat::AbstractMatrix; dim=nothing, query=nothing)

Transform A into a new coordinate system using transformation matrix mat along the dim dimension (time).

source
MinimumVarianceAnalysis.ΔφijMethod
Δφ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
source

Error estimates for MVA:

MinimumVarianceAnalysis.ΔφijFunction
Δφ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
source
MinimumVarianceAnalysis.B_x3_errorFunction

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
source
MinimumVarianceAnalysis.E_x1_errorFunction
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
source

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.