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_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
source
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
source
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.

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

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

See also: mva_eigen, transform

source
MinimumVarianceAnalysis.mva_eigen — Method
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.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)
source
MinimumVarianceAnalysis.transform — Method
transform(A, mat::AbstractMatrix; dim=nothing)

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

source
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
source

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
source
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
source
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
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/CondaPkg/lKlVY/CondaPkg.toml
    CondaPkg Found dependencies: /home/runner/.julia/packages/PySPEDAS/LCxbg/CondaPkg.toml
    CondaPkg Found dependencies: /home/runner/.julia/packages/PythonCall/x9Db0/CondaPkg.toml
    CondaPkg Initialising pixi
             │ /home/runner/.julia/artifacts/8429d049f6c01106c62971e9540e146dc068df25/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]
             │ python-gil = "*"
             │ openssl = ">=3, <3.6"
             │ uv = ">=0.4"
             │ certifi = "*"
             │ netcdf4 = "*"
             │
             │     [dependencies.python]
             │     version = ">=3.10,!=3.14.0,!=3.14.1,<4, <3.14"
             │     build = "*cp*"
             │     channel = "conda-forge"
             │
             │ [pypi-dependencies.pyspedas]
             │ git = "https://github.com/spedas/pyspedas"
             │
             │ [workspace]
             │ name = ".CondaPkg"
             │ description = "automatically generated by CondaPkg.jl"
             │ platforms = ["linux-64"]
             │ channel-priority = "strict"
             └ channels = ["conda-forge"]
    CondaPkg Installing packages
             │ /home/runner/.julia/artifacts/8429d049f6c01106c62971e9540e146dc068df25/bin/pixi
             │ install
             └ --manifest-path /home/runner/work/MinimumVarianceAnalysis.jl/MinimumVarianceAnalysis.jl/docs/.CondaPkg/pixi.toml
✔ The default environment has been installed.
30-Sep-26 20:07:48: 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
30-Sep-26 20:07:48: Download of _testing_output/cotrans_tools/cotrans_tools/mva_python_validate.tplot complete, 2.282 MB in 0.6 sec (3.821 MB/sec) (transfer_normal)
30-Sep-26 20:07:48: del_data: No valid tplot variables found, returning
30-Sep-26 20:07:48: 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.342 μs (2 allocs: 160 bytes), 2.098 ms (3 allocs: 48 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.