
Asteroid orbital elements: an application of multimin
This notebook goes deeper into the multimin module: loading data, building MoGs, and fitting with bounds and multiple components for a particular case, the orbital elements of asteroids.
Installation
If you’re running this in Google Colab or need to install the package, uncomment and run the following cell:
[1]:
import os
import matplotlib.pyplot as plt
os.makedirs('gallery', exist_ok=True)
try:
from google.colab import drive
%pip install -Uq multimin
except ImportError:
print("Not running in Colab, skipping installation")
%load_ext autoreload
%autoreload 2
!mkdir -p gallery/
# Uncomment to install from GitHub (development version)
# !pip install git+https://github.com/seap-udea/MultiMin.git
Not running in Colab, skipping installation
Load the package
Import multimin and other required libraries:
[2]:
import pandas as pd
import numpy as np
import multimin as mn
import warnings
%matplotlib inline
warnings.filterwarnings("ignore")
figprefix="asteroids"
Welcome to MultiMin v0.11.2. ¡Al infinito y más allá!
Asteroid data
multimin was originally developed to solve the problem of describing the distribution of asteroids in the space of orbital elements. This is a true scientific application of the package that illustrate the power of the methods and the versatility of the numerical methods provided by the package.
Load the dataset (e.g. orbital elements):
[3]:
# NEA Data
df_neas=pd.read_json(mn.Util.get_data("nea_data.json.gz"))
# Let's filter 10000 asteroids
df_neas=df_neas.sample(10000)
# Let's select the columns we want to fit
df_neas["q"]=df_neas["a"]*(1-df_neas["e"])
data_neas=np.array(df_neas[["q","e","i","Node","Peri","M"]])
Let’s see the data:
[4]:
properties=dict(
q=dict(label=r"$q$ [au]",range=[0.0,1.3]),
e=dict(label=r"$e$",range=[0.0,1.0]),
i=dict(label=r"$I$ [deg]",range=[0.0,180.0]),
W=dict(label=r"$\Omega$ [deg]",range=[0,360]),
w=dict(label=r"$\omega$ [deg]",range=[0,360]),
)
G=mn.MultiPlot(properties,figsize=2,marginals=True)
sargs=dict(s=0.2,edgecolor='None',color='r')
scatter=G.sample_scatter(data_neas,**sargs)
plt.savefig(f'gallery/{figprefix}_data_neas.png')
Fitting other pair of properties
Fitting \(q\) and \(I\):
[16]:
data_neas_qi=np.array(df_neas[["q","i"]])
F_qi_1 = mn.FitMoG(data=data_neas_qi, ngauss=1, domain=[[0,1.3], [0, 180]])
F_qi_1.fit_data(progress=False)
print(f"-log(L)/N = {F_qi_1.solution.fun/len(data_neas_qi)}")
properties=["q","i"]
sargs=dict(s=0.5,edgecolor='None',color='w')
pargs=dict(cmap='Spectral')
G=F_qi_1.plot_fit(properties=properties,pargs=pargs,sargs=sargs,figsize=4,marginals=True)
plt.savefig(f'gallery/{figprefix}_fit_result_qi_1gauss.png')
Loading a FitMoG object.
Number of gaussians: 1
Number of variables: 2
Number of dimensions: 2
Number of samples: 10000
Domain: [[0, 1.3], [0, 180]]
Log-likelihood per point (-log L/N): 130.8794279820832
FitMoG.fit_data executed in 0.21601486206054688 seconds
-log(L)/N = 3.440885016885199
Fitting \(e\) and \(I\):
[17]:
data_neas_ei=np.array(df_neas[["e","i"]])
F_ei_1 = mn.FitMoG(data=data_neas_ei, ngauss=1, domain=[[0,1], [0, 180]])
F_ei_1.fit_data(progress=False)
print(f"-log(L)/N = {F_ei_1.solution.fun/len(data_neas_ei)}")
properties=["e","I"]
sargs=dict(s=0.5,edgecolor='None',color='r')
G=F_ei_1.plot_fit(properties=properties,sargs=sargs,figsize=4,marginals=True)
plt.savefig(f'gallery/{figprefix}_fit_result_ei_1gauss.png')
Loading a FitMoG object.
Number of gaussians: 1
Number of variables: 2
Number of dimensions: 2
Number of samples: 10000
Domain: [[0, 1], [0, 180]]
Log-likelihood per point (-log L/N): 133.65543285556114
FitMoG.fit_data executed in 0.1379380226135254 seconds
-log(L)/N = 3.284645348500123
Fitting three variables: \(q, e, I\)
Let’s extract first the data:
[18]:
data_neas_qei = np.array(df_neas[["q","e","i"]])
Let’s plot the original data:
[19]:
properties=dict(
q=dict(label=r"$q$", range=[0.0, 1.3]),
e=dict(label=r"$e$", range=[0.0, 1.0]),
i=dict(label=r"$i$", range=[0.0, 180.0]),
)
G = mn.MultiPlot(properties, figsize=2)
sargs = dict(s=0.2, edgecolor='None', color='r')
scatter = G.sample_scatter(data_neas_qei, **sargs)
plt.savefig(f'gallery/{figprefix}_data_neas_qei.png')
Now let’s try to fit this data using truncated multivariate distribution:
[20]:
fit_qei = mn.FitMoG(data=data_neas_qei, ngauss=1, domain=[[0, 1.3], [0, 1.0], [0, 180]])
fit_qei.fit_data(progress="tqdm", normalize=True)
print(f"-log(L)/N = {fit_qei.solution.fun/len(data_neas_qei)}")
properties=["q","e","i"]
pargs=dict(cmap='YlGn')
sargs=dict(s=0.5,edgecolor='None',color='r')
G=fit_qei.plot_fit(
properties=properties,
sargs=sargs,
pargs=pargs,
figsize=3
)
plt.savefig(f'gallery/{figprefix}_fit_result_qei_1gauss_simple.png')
Loading a FitMoG object.
Number of gaussians: 1
Number of variables: 3
Number of dimensions: 3
Number of samples: 10000
Domain: [[0, 1.3], [0, 1.0], [0, 180]]
Log-likelihood per point (-log L/N): 143.82266197121922
FitMoG.fit_data executed in 1.4659969806671143 seconds
-log(L)/N = -0.7355306439187119
As you see, without information the fit is not too successful. We will try a different approach.
Initial parameters from partial fits. The 1-Gaussian fit in (q,e,i) often misses the q–e correlation when started from generic initial values. We use the three 2D fits (F_qe_1, F_qi_1, F_ei_1) to build initial means, sigmas, and correlations for the full 3D fit: each mean/sigma is averaged over the two partial fits that contain that variable; each correlation comes from the single partial fit that contains that pair.
[21]:
# Initial (mus, sigmas, rhos) from partial fits F_qe_1, F_qi_1, F_ei_1 (vars: 0=q, 1=e, 2=i)
mu_q = (F_qe_1.mog.mus[0, 0] + F_qi_1.mog.mus[0, 0]) / 2
mu_e = (F_qe_1.mog.mus[0, 1] + F_ei_1.mog.mus[0, 0]) / 2
mu_i = (F_qi_1.mog.mus[0, 1] + F_ei_1.mog.mus[0, 1]) / 2
sigma_q = (F_qe_1.mog.sigmas[0, 0] + F_qi_1.mog.sigmas[0, 0]) / 2
sigma_e = (F_qe_1.mog.sigmas[0, 1] + F_ei_1.mog.sigmas[0, 0]) / 2
sigma_i = (F_qi_1.mog.sigmas[0, 1] + F_ei_1.mog.sigmas[0, 1]) / 2
rho_qe = float(F_qe_1.mog.rhos[0, 0])
rho_qi = float(F_qi_1.mog.rhos[0, 0])
rho_ei = float(F_ei_1.mog.rhos[0, 0])
fit_qei = mn.FitMoG(data=data_neas_qei, ngauss=1, domain=[[0, 1.3], [0, 1.0], [0, 180]])
fit_qei.set_initial_params(
mus=[mu_q, mu_e, mu_i],
sigmas=[sigma_q, sigma_e, sigma_i],
rhos=[rho_qe, rho_qi, rho_ei],
)
#fit_qei.set_bounds(boundsm=((0.8, 1.2), (0.0, 1.0), (0.0, 15.0)))
fit_qei.fit_data(progress="tqdm", normalize=True)
print(f"-log(L)/N = {fit_qei.solution.fun/len(data_neas_qei)}")
Loading a FitMoG object.
Number of gaussians: 1
Number of variables: 3
Number of dimensions: 3
Number of samples: 10000
Domain: [[0, 1.3], [0, 1.0], [0, 180]]
Log-likelihood per point (-log L/N): 143.82266143089612
FitMoG.fit_data executed in 0.5155620574951172 seconds
-log(L)/N = -2.3983401612611557
[22]:
properties=["q","e","i"]
pargs=dict(cmap='YlGn')
sargs=dict(s=0.5,edgecolor='None',color='r')
G=fit_qei.plot_fit(
properties=properties,
pargs=pargs,
sargs=sargs,
figsize=3,
marginals=True
)
plt.savefig(f'gallery/{figprefix}_fit_result_qei_1gauss_feed.png')
Much better!
Let’s try with more gaussians:
[23]:
fit_qei = mn.FitMoG(data=data_neas_qei, ngauss=5, domain=[[0.0, 1.3], [0.0, 1.0], [0.0, 180.0]])
fit_qei.set_initial_params(
mus=[mu_q, mu_e, mu_i],
sigmas=[sigma_q, sigma_e, sigma_i],
rhos=[rho_qe, rho_qi, rho_ei],
)
fit_qei.fit_data(progress="tqdm")
G=fit_qei.plot_fit(
properties=properties,
pargs=pargs,
sargs=sargs,
figsize=3
)
plt.savefig(f'gallery/{figprefix}_fit_result_qei_ngauss.png')
Loading a FitMoG object.
Number of gaussians: 5
Number of variables: 3
Number of dimensions: 15
Number of samples: 10000
Domain: [[0.0, 1.3], [0.0, 1.0], [0.0, 180.0]]
Log-likelihood per point (-log L/N): 338.17600944753315
FitMoG.fit_data executed in 10.4010009765625 seconds
The problem is that the fit is not converging to a good representation of the distribution.
Transforming data
Orbital elements such as \(q\), \(e\), and \(i\) live in finite intervals (e.g. \(q \in [0, q_{\max}]\), \(e \in [0, 1)\), \(i \in [0, \pi]\)), while the MoG is defined on the whole real line. To fit a normal mixture on unbounded variables we first map each bounded variable to an unbounded one via a logistic-type (log-odds) transformation (see e.g. the manuscript-neoflux formalism).
For a variable \(x \in (0, x_{\max})\), define the unbound variable:
so that \(u \in (-\infty, +\infty)\). The inverse map is:
In the notebook we use this with \(q_{\max}=1.35\) au, \(e_{\max}=1\), \(i_{\max}=\pi\) to obtain unbound variables \((Q, C, I)\). Fitting the MoG in \((Q, C, I)\) and then transforming back preserves normalization and often improves conditioning; the same transformation is used in the manuscript for the NEO flux formalism.
Transform variables to an unbounded scale for fitting (e.g. with Util.t_if / f2u):
[24]:
scales=[1.35,1.00,180.0]
udata=np.zeros_like(data_neas_qei)
for i in range(len(data_neas_qei)):
udata[i]=mn.Util.t_if(data_neas_qei[i],scales,mn.Util.f2u)
Visualize the data with MultiPlot (scatter on pairwise panels):
[25]:
properties=dict(
Q=dict(label=r"$Q$",range=None),
E=dict(label=r"$C$",range=None),
I=dict(label=r"$I$",range=None),
)
G=mn.MultiPlot(properties,figsize=3)
sargs=dict(s=0.2,edgecolor='None',color='r')
hist=G.sample_scatter(udata,**sargs)
plt.savefig('gallery/indepth_data_scatter_QCI.png')
The same idea (initial parameters from partial fits) can be reused for multi-component fits below.
You can also show 2D histograms on the panels:
[26]:
G=mn.MultiPlot(properties,figsize=3)
hargs=dict(bins=30,cmap='Spectral_r')
hist=G.sample_hist(udata,colorbar=True,**hargs)
plt.savefig('gallery/multimin_indepth_2.png')
Create the fitter (e.g. one Gaussian, three variables):
[27]:
F=mn.FitMoG(data=udata, ngauss=1)
Loading a FitMoG object.
Number of gaussians: 1
Number of variables: 3
Number of dimensions: 3
Number of samples: 10000
Log-likelihood per point (-log L/N): 14.01292420165598
The fitter holds an initial MoG that will be optimized:
[28]:
print(F.mog)
Composition of ngauss = 1 gaussian multivariates of nvars = 3 random variables:
Weights: [1.0]
Number of variables: 3
Averages (μ): [[0.5, 0.5, 0.5]]
Standard deviations (σ): [[1.0000000000000002, 1.0000000000000002, 1.0000000000000002]]
Correlation coefficients (ρ): [[0.5, 0.5, 0.5]]
Covariant matrices (Σ):
[[[1.0000000000000004, 0.5000000000000002, 0.5000000000000002], [0.5000000000000002, 1.0000000000000004, 0.5000000000000002], [0.5000000000000002, 0.5000000000000002, 1.0000000000000004]]]
Flatten parameters:
With covariance matrix (10):
[p1,μ1_1,μ1_2,μ1_3,Σ1_11,Σ1_12,Σ1_13,Σ1_22,Σ1_23,Σ1_33]
[1.0, 0.5, 0.5, 0.5, 1.0000000000000004, 0.5000000000000002, 0.5000000000000002, 1.0000000000000004, 0.5000000000000002, 1.0000000000000004]
With std. and correlations (10):
[p1,μ1_1,μ1_2,μ1_3,σ1_1,σ1_2,σ1_3,ρ1_12,ρ1_13,ρ1_23]
[1.0, 0.5, 0.5, 0.5, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 0.5, 0.5, 0.5]
Run the minimization:
[29]:
t = mn.Util.el_time(0)
F.fit_data(verbose=False,progress="tqdm")
t = mn.Util.el_time()
print(f"-log(L)/N = {F.solution.fun/len(udata)}")
FitMoG.fit_data executed in 0.21823716163635254 seconds
Elapsed time since last call: 218.496 ms
-log(L)/N = 3.95585372599441
Inspect the fitted MoG:
[30]:
print(F.mog)
Composition of ngauss = 1 gaussian multivariates of nvars = 3 random variables:
Weights: [1.0]
Number of variables: 3
Averages (μ): [[0.8605729678122919, -0.3384122690377656, -3.0757098168746153]]
Standard deviations (σ): [[0.8407250580859433, 0.8684129810087118, 1.0693770849265927]]
Correlation coefficients (ρ): [[-0.30984044642884734, 0.023097072434971496, -0.0645086173249525]]
Covariant matrices (Σ):
[[[0.7068186232936128, -0.2262134421968911, 0.02076547174999843], [-0.2262134421968911, 0.7541411055844373, -0.05990663334136546], [0.02076547174999843, -0.05990663334136546, 1.143567349766097]]]
Flatten parameters:
With covariance matrix (10):
[p1,μ1_1,μ1_2,μ1_3,Σ1_11,Σ1_12,Σ1_13,Σ1_22,Σ1_23,Σ1_33]
[1.0, 0.8605729678122919, -0.3384122690377656, -3.0757098168746153, 0.7068186232936128, -0.2262134421968911, 0.02076547174999843, 0.7541411055844373, -0.05990663334136546, 1.143567349766097]
With std. and correlations (10):
[p1,μ1_1,μ1_2,μ1_3,σ1_1,σ1_2,σ1_3,ρ1_12,ρ1_13,ρ1_23]
[1.0, 0.8605729678122919, -0.3384122690377656, -3.0757098168746153, 0.8407250580859433, 0.8684129810087118, 1.0693770849265927, -0.30984044642884734, 0.0230970724349715, -0.0645086173249525]
Plot the fit result (fitted sample + data scatter):
[31]:
properties=["Q","C","I"]
pargs=dict(cmap='YlGn')
sargs=dict(s=0.2,edgecolor='None',color='r')
cargs=dict()
G=F.plot_fit(properties=properties,sargs=sargs,cargs=cargs,pargs=pargs,figsize=3)
plt.savefig(f'gallery/{figprefix}_indepth_fit_result_QCI.png')
Fitting can be time-consuming; you can save the result for later use:
[32]:
F.save_fit(f"gallery/{figprefix}_fit-single.pkl",useprefix=False)
Load a previously saved fit (here or in another notebook):
[33]:
F=mn.FitMoG(f"gallery/{figprefix}_fit-single.pkl")
print(F.mog)
Loading a FitMoG object.
Number of gaussians: 1
Number of variables: 3
Number of dimensions: 3
Number of samples: 10000
Log-likelihood per point (-log L/N): 3.95585372599441
Composition of ngauss = 1 gaussian multivariates of nvars = 3 random variables:
Weights: [1.0]
Number of variables: 3
Averages (μ): [[0.8605729678122919, -0.3384122690377656, -3.0757098168746153]]
Standard deviations (σ): [[0.8407250580859433, 0.8684129810087118, 1.0693770849265927]]
Correlation coefficients (ρ): [[-0.30984044642884734, 0.023097072434971496, -0.0645086173249525]]
Covariant matrices (Σ):
[[[0.7068186232936128, -0.2262134421968911, 0.02076547174999843], [-0.2262134421968911, 0.7541411055844373, -0.05990663334136546], [0.02076547174999843, -0.05990663334136546, 1.143567349766097]]]
Flatten parameters:
With covariance matrix (10):
[p1,μ1_1,μ1_2,μ1_3,Σ1_11,Σ1_12,Σ1_13,Σ1_22,Σ1_23,Σ1_33]
[1.0, 0.8605729678122919, -0.3384122690377656, -3.0757098168746153, 0.7068186232936128, -0.2262134421968911, 0.02076547174999843, 0.7541411055844373, -0.05990663334136546, 1.143567349766097]
With std. and correlations (10):
[p1,μ1_1,μ1_2,μ1_3,σ1_1,σ1_2,σ1_3,ρ1_12,ρ1_13,ρ1_23]
[1.0, 0.8605729678122919, -0.3384122690377656, -3.0757098168746153, 0.8407250580859433, 0.8684129810087118, 1.0693770849265927, -0.30984044642884734, 0.0230970724349715, -0.0645086173249525]
Fit with two Gaussian components for a better likelihood:
[34]:
F=mn.FitMoG(data=udata, ngauss=2)
mn.Util.el_time(0)
F.fit_data(progress="tqdm")
mn.Util.el_time()
F.save_fit(f"gallery/{figprefix}_fit-multiple.pkl",useprefix=False)
print(f"-log(L)/N = {F.solution.fun/len(udata)}")
print(F.mog)
G=F.plot_fit(figsize=4,
properties=["Q","C","I"],
pargs=dict(cmap='YlGn'),sargs=dict(s=0.2,edgecolor='None',color='r'),cargs=dict())
F.fig.savefig(f"gallery/{figprefix}_fit-multiple-{F.prefix}.png")
Loading a FitMoG object.
Number of gaussians: 2
Number of variables: 3
Number of dimensions: 6
Number of samples: 10000
Log-likelihood per point (-log L/N): 14.01292420165598
FitMoG.fit_data executed in 1.8731448650360107 seconds
Elapsed time since last call: 1.87351 s
-log(L)/N = 3.8404988081396585
Composition of ngauss = 2 gaussian multivariates of nvars = 3 random variables:
Weights: [0.36045330359049493, 0.639546696409505]
Number of variables: 3
Averages (μ): [[1.0026118181785064, 0.31827278999916386, -3.0946497216154105], [0.7708183357341977, -0.694124428098996, -3.074231719535267]]
Standard deviations (σ): [[1.1014859873617338, 0.557152428345854, 0.9606825163856727], [0.6480012558609982, 0.7952126130037853, 1.123458227911257]]
Correlation coefficients (ρ): [[-0.8124045633268461, -0.053973868624144994, 0.11595319201107457], [-0.32383437385601055, 0.10588533200328687, -0.13892503758133723]]
Covariant matrices (Σ):
[[[1.2132713803542534, -0.498569099960419, -0.057113978169854765], [-0.498569099960419, 0.31041882841168195, 0.06206355142056842], [-0.057113978169854765, 0.06206355142056842, 0.9229108972891082]], [[0.4199056275974309, -0.16687145514796586, 0.0770847697447221], [-0.16687145514796586, 0.632363099880308, -0.124113982732735], [0.0770847697447221, -0.124113982732735, 1.262158389861502]]]
Flatten parameters:
With covariance matrix (20):
[p1,p2,μ1_1,μ1_2,μ1_3,μ2_1,μ2_2,μ2_3,Σ1_11,Σ1_12,Σ1_13,Σ1_22,Σ1_23,Σ1_33,Σ2_11,Σ2_12,Σ2_13,Σ2_22,Σ2_23,Σ2_33]
[0.36045330359049493, 0.639546696409505, 1.0026118181785064, 0.31827278999916386, -3.0946497216154105, 0.7708183357341977, -0.694124428098996, -3.074231719535267, 1.2132713803542534, -0.498569099960419, -0.057113978169854765, 0.31041882841168195, 0.06206355142056842, 0.9229108972891082, 0.4199056275974309, -0.16687145514796586, 0.0770847697447221, 0.632363099880308, -0.124113982732735, 1.262158389861502]
With std. and correlations (20):
[p1,p2,μ1_1,μ1_2,μ1_3,μ2_1,μ2_2,μ2_3,σ1_1,σ1_2,σ1_3,σ2_1,σ2_2,σ2_3,ρ1_12,ρ1_13,ρ1_23,ρ2_12,ρ2_13,ρ2_23]
[0.2843457865270869, 0.5045103113216581, 1.0026118181785064, 0.31827278999916386, -3.0946497216154105, 0.7708183357341977, -0.694124428098996, -3.074231719535267, 1.1014859873617338, 0.557152428345854, 0.9606825163856727, 0.6480012558609982, 0.7952126130037853, 1.123458227911257, -0.8124045633268461, -0.053973868624144994, 0.11595319201107457, -0.32383437385601055, 0.10588533200328687, -0.13892503758133723]
Using two components improves the fit (lower \(-\log\mathcal{L}/N\)).
You can set bounds on parameters (e.g. means, weights) to avoid unphysical solutions:
[35]:
F=mn.FitMoG(data=udata, ngauss=2)
bounds=F.set_bounds(boundsm=((-2,4),(-4,3),(-7,0)),boundw=(0.1,0.9))
mn.Util.el_time(0)
F.fit_data(udata,progress="tqdm",bounds=bounds)
mn.Util.el_time()
F.save_fit(f"gallery/{figprefix}_fit-multiple-bounds.pkl",useprefix=False)
print(f"-log(L)/N = {F.solution.fun/len(udata)}")
print(F.mog)
G=F.plot_fit(figsize=4,
properties=["Q","C","I"],
pargs=dict(cmap='YlGn'),sargs=dict(s=0.2,edgecolor='None',color='r'),cargs=dict())
F.fig.savefig("gallery/indepth_fit_2gauss_bounds_QCI.png")
Loading a FitMoG object.
Number of gaussians: 2
Number of variables: 3
Number of dimensions: 6
Number of samples: 10000
Log-likelihood per point (-log L/N): 14.01292420165598
FitMoG.fit_data executed in 1.965381145477295 seconds
Elapsed time since last call: 1.9687 s
-log(L)/N = 3.839780193801437
Composition of ngauss = 2 gaussian multivariates of nvars = 3 random variables:
Weights: [0.3791114486700117, 0.6208885513299884]
Number of variables: 3
Averages (μ): [[1.0079402818976861, 0.3028131869437181, -3.1731636484271895], [0.7609936704853688, -0.7161784251019154, -3.0254743868039076]]
Standard deviations (σ): [[1.0740783840394197, 0.549258710361431, 1.0223237355187231], [0.6529226408046985, 0.7954665923205886, 1.0913620699972684]]
Correlation coefficients (ρ): [[-0.7969630548302042, -0.03387040888394299, 0.11767523609041185], [-0.34344704851069163, 0.10759475710072051, -0.08993338189331923]]
Covariant matrices (Σ):
[[[1.153644375060731, -0.47016589002284326, -0.037191599797617415], [-0.47016589002284326, 0.3016851309079024, 0.0660770240512243], [-0.037191599797617415, 0.0660770240512243, 1.0451458202049562]], [[0.42630797487538136, -0.17837889203615376, 0.07666933455929745], [-0.17837889203615376, 0.6327670994981295, -0.07807495203195965], [0.07666933455929745, -0.07807495203195965, 1.1910711678287225]]]
Flatten parameters:
With covariance matrix (20):
[p1,p2,μ1_1,μ1_2,μ1_3,μ2_1,μ2_2,μ2_3,Σ1_11,Σ1_12,Σ1_13,Σ1_22,Σ1_23,Σ1_33,Σ2_11,Σ2_12,Σ2_13,Σ2_22,Σ2_23,Σ2_33]
[0.3791114486700117, 0.6208885513299884, 1.0079402818976861, 0.3028131869437181, -3.1731636484271895, 0.7609936704853688, -0.7161784251019154, -3.0254743868039076, 1.153644375060731, -0.47016589002284326, -0.037191599797617415, 0.3016851309079024, 0.0660770240512243, 1.0451458202049562, 0.42630797487538136, -0.17837889203615376, 0.07666933455929745, 0.6327670994981295, -0.07807495203195965, 1.1910711678287225]
With std. and correlations (20):
[p1,p2,μ1_1,μ1_2,μ1_3,μ2_1,μ2_2,μ2_3,σ1_1,σ1_2,σ1_3,σ2_1,σ2_2,σ2_3,ρ1_12,ρ1_13,ρ1_23,ρ2_12,ρ2_13,ρ2_23]
[0.5495314857222495, 0.8999934169154861, 1.0079402818976861, 0.3028131869437181, -3.1731636484271895, 0.7609936704853688, -0.7161784251019154, -3.0254743868039076, 1.0740783840394197, 0.549258710361431, 1.0223237355187231, 0.6529226408046985, 0.7954665923205886, 1.0913620699972684, -0.7969630548302042, -0.03387040888394299, 0.11767523609041186, -0.34344704851069163, 0.10759475710072053, -0.08993338189331923]
Fitting with five Gaussians can capture more structure:
[36]:
F=mn.FitMoG(data=udata, ngauss=5)
bounds=F.set_bounds(boundsm=((-2,4),(-4,3),(-7,0)),boundw=(0.1,0.9))
mn.Util.el_time(0)
F.fit_data(progress="tqdm",bounds=bounds)
mn.Util.el_time()
F.save_fit(f"gallery/fit-multiple-bound_mus.pkl",useprefix=False)
print(f"-log(L)/N = {F.solution.fun/len(udata)}")
print(F.mog)
G=F.plot_fit(figsize=4,
marginals=True,
properties=["Q","C","I"],
pargs=dict(cmap='YlGn'),sargs=dict(s=0.2,edgecolor='None',color='r'),cargs=dict())
F.fig.savefig(f"gallery/{figprefix}_fit_5gauss_bound_mus_QCI.png")
Loading a FitMoG object.
Number of gaussians: 5
Number of variables: 3
Number of dimensions: 15
Number of samples: 10000
Log-likelihood per point (-log L/N): 14.01292420165598
FitMoG.fit_data executed in 11.450128078460693 seconds
Elapsed time since last call: 11.4527 s
-log(L)/N = 3.712181108119883
Composition of ngauss = 5 gaussian multivariates of nvars = 3 random variables:
Weights: [0.1536229292340353, 0.24508497648076236, 0.20152138978789494, 0.17239440635356557, 0.22737629814374175]
Number of variables: 3
Averages (μ): [[1.241654882712849, -0.7591823186846021, -3.5439971483302286], [1.0000987827669763, -0.722003626795532, -2.61877548473865], [0.6970185650046709, 0.4103566811593673, -3.628249226971697], [1.6126251927313169, 0.01510379661213535, -3.125787945696569], [-0.004200227790548048, -0.536116672389023, -2.7494974986973046]]
Standard deviations (σ): [[0.7391826401831176, 0.672439276529789, 1.3298972744243418], [0.3588412188805859, 0.633493222764143, 0.7704691777600665], [0.5703663173450618, 0.3567566676733523, 1.137790587649432], [0.8104834720316584, 0.2843111455885323, 0.7786488288329858], [0.7120676285689942, 1.2289709860878721, 0.91283351372234]]
Correlation coefficients (ρ): [[-0.20229859464979708, 0.6520161329450087, -6.460371240857299e-06], [-0.41809532974142083, 0.2360128836218103, -0.24084783239774077], [-0.7325135303601764, -0.12925403265731272, 0.22924991262448335], [-0.3043799415289591, 0.11669439066321452, 0.2638188001542039], [-0.8549398994026514, -0.17773915781258698, 0.2199744869481266]]
Covariant matrices (Σ):
[[[0.5463909755480844, -0.10055361693217248, 0.6409559692513341], [-0.10055361693217248, 0.452174580619906, -5.777349532008022e-06], [0.6409559692513341, -5.777349532008022e-06, 1.768626760521293]], [[0.12876702036770457, -0.09504288541606443, 0.0652519213438307], [-0.09504288541606443, 0.4013136632881001, -0.11755469656392081], [0.0652519213438307, -0.11755469656392081, 0.593622753878273]], [[0.3253177359617678, -0.14905330846366932, -0.08388036451274704], [-0.14905330846366932, 0.12727531992939473, 0.09305583581788235], [-0.08388036451274704, 0.09305583581788235, 1.29456742134364]], [[0.656883458436492, -0.07013811299244864, 0.07364373018205701], [-0.07013811299244864, 0.08083282750586362, 0.058403820944274445], [0.07364373018205701, 0.058403820944274445, 0.6062939986429804]], [[0.507040307655871, -0.7481668449142242, -0.11553030956828987], [-0.7481668449142242, 1.5103696846457968, 0.2467774770558185], [-0.11553030956828987, 0.2467774770558185, 0.8332650237746735]]]
Flatten parameters:
With covariance matrix (50):
[p1,p2,p3,p4,p5,μ1_1,μ1_2,μ1_3,μ2_1,μ2_2,μ2_3,μ3_1,μ3_2,μ3_3,μ4_1,μ4_2,μ4_3,μ5_1,μ5_2,μ5_3,Σ1_11,Σ1_12,Σ1_13,Σ1_22,Σ1_23,Σ1_33,Σ2_11,Σ2_12,Σ2_13,Σ2_22,Σ2_23,Σ2_33,Σ3_11,Σ3_12,Σ3_13,Σ3_22,Σ3_23,Σ3_33,Σ4_11,Σ4_12,Σ4_13,Σ4_22,Σ4_23,Σ4_33,Σ5_11,Σ5_12,Σ5_13,Σ5_22,Σ5_23,Σ5_33]
[0.15362292923403534, 0.2450849764807624, 0.201521389787895, 0.1723944063535656, 0.2273762981437418, 1.241654882712849, -0.7591823186846021, -3.5439971483302286, 1.0000987827669763, -0.722003626795532, -2.61877548473865, 0.6970185650046709, 0.4103566811593673, -3.628249226971697, 1.6126251927313169, 0.01510379661213535, -3.125787945696569, -0.004200227790548048, -0.536116672389023, -2.7494974986973046, 0.5463909755480844, -0.10055361693217248, 0.6409559692513341, 0.452174580619906, -5.777349532008022e-06, 1.768626760521293, 0.12876702036770457, -0.09504288541606443, 0.0652519213438307, 0.4013136632881001, -0.11755469656392081, 0.593622753878273, 0.3253177359617678, -0.14905330846366932, -0.08388036451274704, 0.12727531992939473, 0.09305583581788235, 1.29456742134364, 0.656883458436492, -0.07013811299244864, 0.07364373018205701, 0.08083282750586362, 0.058403820944274445, 0.6062939986429804, 0.507040307655871, -0.7481668449142242, -0.11553030956828987, 1.5103696846457968, 0.2467774770558185, 0.8332650237746735]
With std. and correlations (50):
[p1,p2,p3,p4,p5,μ1_1,μ1_2,μ1_3,μ2_1,μ2_2,μ2_3,μ3_1,μ3_2,μ3_3,μ4_1,μ4_2,μ4_3,μ5_1,μ5_2,μ5_3,σ1_1,σ1_2,σ1_3,σ2_1,σ2_2,σ2_3,σ3_1,σ3_2,σ3_3,σ4_1,σ4_2,σ4_3,σ5_1,σ5_2,σ5_3,ρ1_12,ρ1_13,ρ1_23,ρ2_12,ρ2_13,ρ2_23,ρ3_12,ρ3_13,ρ3_23,ρ4_12,ρ4_13,ρ4_23,ρ5_12,ρ5_13,ρ5_23]
[0.4855097994771264, 0.7745663904426356, 0.6368880611384412, 0.544835162804292, 0.7185998956538683, 1.241654882712849, -0.7591823186846021, -3.5439971483302286, 1.0000987827669763, -0.722003626795532, -2.61877548473865, 0.6970185650046709, 0.4103566811593673, -3.628249226971697, 1.6126251927313169, 0.01510379661213535, -3.125787945696569, -0.004200227790548048, -0.536116672389023, -2.7494974986973046, 0.7391826401831176, 0.672439276529789, 1.3298972744243418, 0.3588412188805859, 0.633493222764143, 0.7704691777600665, 0.5703663173450618, 0.3567566676733523, 1.137790587649432, 0.8104834720316584, 0.2843111455885323, 0.7786488288329858, 0.7120676285689942, 1.2289709860878721, 0.91283351372234, -0.20229859464979705, 0.6520161329450087, -6.460371240857299e-06, -0.41809532974142083, 0.23601288362181028, -0.24084783239774077, -0.7325135303601764, -0.12925403265731272, 0.22924991262448335, -0.3043799415289591, 0.11669439066321452, 0.2638188001542039, -0.8549398994026512, -0.17773915781258698, 0.2199744869481266]
As you can see the fitting parameter \(-\log{\cal L}\) is improved with respect to previous fit.
Verify the fit by generating a sample from the fitted MoG and comparing to the data:
[37]:
F.mog.plot_sample(N=len(F.data),
figsize=3,
properties=["Q","C","I"],ranges=[[-5,5],[-6,6],[-8,2]],
sargs=dict(s=0.2,edgecolor='None',color='r'))
G.fig.savefig(f"gallery/{figprefix}_sample_from_fit_20gauss_QCI.png")
properties=dict(
Q=dict(label=r"$Q$",range=[-5,5]),
C=dict(label=r"$C$",range=[-6,6]),
I=dict(label=r"$I$",range=[-8,2]),
)
G=mn.MultiPlot(properties,figsize=3)
sargs=dict(s=0.2,edgecolor='None',color='r')
hist=G.sample_scatter(udata,**sargs)
MixtureOfGaussians.rvs executed in 0.6000351905822754 seconds
Let’s compare them through its marginals:
[38]:
properties=dict(
Q=dict(label=r"$Q$",range=[-5,5]),
C=dict(label=r"$C$",range=[-6,6]),
I=dict(label=r"$I$",range=[-8,2]),
)
G=mn.MultiPlot(properties,figsize=3,marginals=True)
sargs=dict(s=0.2,edgecolor='None',color='r',margs=dict(color='r'))
hist=G.sample_scatter(udata,**sargs)
sample = F.mog.rvs(len(udata))
sargs=dict(s=0.2,edgecolor='None',color='b',margs=dict(color='b'))
G.sample_scatter(sample,**sargs)
MixtureOfGaussians.rvs executed in 0.562633752822876 seconds
[38]:
[<matplotlib.collections.PathCollection at 0x12c477a40>,
<matplotlib.collections.PathCollection at 0x12fcdb8c0>,
<matplotlib.collections.PathCollection at 0x12fb88cb0>]
We can also check the original distribution:
[39]:
scales=[1.35,1.00,180.0]
usample = F.mog.rvs(len(udata))
rsample=np.zeros_like(usample)
for i in range(len(usample)):
rsample[i] = mn.Util.t_if(usample[i], scales, mn.Util.u2f)
MixtureOfGaussians.rvs executed in 0.5577900409698486 seconds
[40]:
properties=dict(
q=dict(label=r"$q$", range=[0.0, 1.3]),
e=dict(label=r"$e$", range=[0.0, 1.0]),
i=dict(label=r"$i$", range=[0.0, 180.0]),
)
Gt=mn.MultiPlot(properties,figsize=3)
sargs=dict(s=0.2,edgecolor='None',color='b')
scatter_transformed=Gt.sample_scatter(rsample,**sargs)
Gt.fig.suptitle(f"Transformed Sample from Fit with {F.ngauss} Gaussians")
Gt.fig.tight_layout()
Gt.fig.savefig(f"gallery/{figprefix}_sample_from_fit_ngauss_qei.png")
Go=mn.MultiPlot(properties,figsize=3)
sargs=dict(s=0.2,edgecolor='None',color='r')
scatter_original=Go.sample_scatter(data_neas_qei,**sargs)
And their marginals:
[41]:
properties=dict(
q=dict(label=r"$q$", range=[0.0, 1.3]),
e=dict(label=r"$e$", range=[0.0, 1.0]),
i=dict(label=r"$i$", range=[0.0, 180.0]),
)
Gt=mn.MultiPlot(properties,figsize=3, marginals=True)
sargs=dict(s=0.2,edgecolor='None',color='b',margs=dict(color='b'))
scatter_transformed=Gt.sample_scatter(rsample,**sargs)
sargs=dict(s=0.2,edgecolor='None',color='r',margs=dict(color='r'))
scatter_original=Gt.sample_scatter(data_neas_qei,**sargs)
[42]:
function, mog = F.mog.get_function(properties=properties)
from multimin import Util
def mog(X):
mu1_q = 1.241655
mu1_e = -0.759182
mu1_i = -3.543997
mu1 = [mu1_q, mu1_e, mu1_i]
Sigma1 = [[0.546391, -0.100554, 0.640956], [-0.100554, 0.452175, -6e-06], [0.640956, -6e-06, 1.768627]]
n1 = Util.nmd(X, mu1, Sigma1)
mu2_q = 1.000099
mu2_e = -0.722004
mu2_i = -2.618775
mu2 = [mu2_q, mu2_e, mu2_i]
Sigma2 = [[0.128767, -0.095043, 0.065252], [-0.095043, 0.401314, -0.117555], [0.065252, -0.117555, 0.593623]]
n2 = Util.nmd(X, mu2, Sigma2)
mu3_q = 0.697019
mu3_e = 0.410357
mu3_i = -3.628249
mu3 = [mu3_q, mu3_e, mu3_i]
Sigma3 = [[0.325318, -0.149053, -0.08388], [-0.149053, 0.127275, 0.093056], [-0.08388, 0.093056, 1.294567]]
n3 = Util.nmd(X, mu3, Sigma3)
mu4_q = 1.612625
mu4_e = 0.015104
mu4_i = -3.125788
mu4 = [mu4_q, mu4_e, mu4_i]
Sigma4 = [[0.656883, -0.070138, 0.073644], [-0.070138, 0.080833, 0.058404], [0.073644, 0.058404, 0.606294]]
n4 = Util.nmd(X, mu4, Sigma4)
mu5_q = -0.0042
mu5_e = -0.536117
mu5_i = -2.749497
mu5 = [mu5_q, mu5_e, mu5_i]
Sigma5 = [[0.50704, -0.748167, -0.11553], [-0.748167, 1.51037, 0.246777], [-0.11553, 0.246777, 0.833265]]
n5 = Util.nmd(X, mu5, Sigma5)
w1 = 0.153623
w2 = 0.245085
w3 = 0.201521
w4 = 0.172394
w5 = 0.227376
return (
w1*n1
+ w2*n2
+ w3*n3
+ w4*n4
+ w5*n5
)
MultiMin - Multivariate Gaussian fitting
© 2026 Jorge I. Zuluaga