gx2 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gx2-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Abhranil Das
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gx2-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: gx2
3
+ Version: 1.0.0
4
+ Summary: Statistics, pdf, cdf, inverse cdf and random numbers of the generalized chi-square distribution
5
+ Author-email: Abhranil Das <abhranil.das@utexas.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Reference paper 1, https://arxiv.org/abs/2012.14331
8
+ Project-URL: Reference paper 2, https://arxiv.org/abs/2404.05062
9
+ Keywords: generalized chi-square,chi-square,quadratic form,normal distribution,statistics
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: numpy>=1.21
24
+ Requires-Dist: scipy>=1.7
25
+ Requires-Dist: mpmath>=1.2
26
+ Provides-Extra: plot
27
+ Requires-Dist: matplotlib>=3.4; extra == "plot"
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=7.0; extra == "test"
30
+ Provides-Extra: docs
31
+ Requires-Dist: jupyter; extra == "docs"
32
+ Requires-Dist: matplotlib>=3.4; extra == "docs"
33
+ Dynamic: license-file
34
+
35
+ <p align="center">
36
+ <img src="https://raw.githubusercontent.com/abhranildas/gx2-py/main/gx2_icon.png" alt="gx2" width="260">
37
+ </p>
38
+
39
+ # gx2 — Generalized chi-square distribution
40
+
41
+ `gx2` computes the statistics, characteristic function, pdf, cdf, inverse cdf
42
+ and random numbers of the **generalized chi-square distribution**.
43
+
44
+ A generalized chi-square variable is a weighted sum of independent non-central
45
+ chi-square variables plus a normal variable — equivalently, the quadratic form
46
+ of a normal random vector. It is parametrized by:
47
+
48
+ | parameter | meaning |
49
+ |-----------|---------|
50
+ | `w` | weights of the non-central chi-square terms |
51
+ | `k` | their degrees of freedom |
52
+ | `lambda_` | their non-centralities (named `lambda_` because `lambda` is a Python keyword) |
53
+ | `s` | scale (standard deviation) of the added normal term |
54
+ | `m` | constant offset |
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install gx2
60
+ ```
61
+
62
+ Requires `numpy`, `scipy` and `mpmath`. `matplotlib` is optional, for plotting
63
+ in the getting-started notebook.
64
+
65
+ To install from a local clone instead:
66
+
67
+ ```bash
68
+ pip install .
69
+ # or, for development (editable install with test/plot extras):
70
+ pip install -e ".[plot,test]"
71
+ ```
72
+
73
+ ## Getting started
74
+
75
+ ```python
76
+ import gx2
77
+
78
+ w, k, lambda_, s, m = [1, -5, 2], [1, 2, 3], [2, 3, 7], 0, 5
79
+
80
+ gx2.stat(w, k, lambda_, s, m) # mean and variance
81
+ gx2.cdf(25, w, k, lambda_, s, m) # cdf at x = 25
82
+ gx2.pdf(25, w, k, lambda_, s, m) # pdf at x = 25
83
+ gx2.inv(0.9, w, k, lambda_, s, m) # 90th percentile
84
+ gx2.rnd(w, k, lambda_, s, m, size=5) # random numbers
85
+ ```
86
+
87
+ Open [`GettingStarted.ipynb`](GettingStarted.ipynb) for an interactive tour
88
+ with worked examples and plots. For any function, see its full documentation
89
+ with `help(gx2.cdf)` (or `gx2.cdf?` in Jupyter).
90
+
91
+ ## Public functions
92
+
93
+ | function | purpose |
94
+ |----------|---------|
95
+ | `stat(w, k, lambda_, s, m)` | mean and variance |
96
+ | `char(t, w, k, lambda_, s, m)` | characteristic function |
97
+ | `rnd(w, k, lambda_, s, m, size=, method=)` | random numbers |
98
+ | `cdf(x, w, k, lambda_, s, m, side=, method=, ...)` | cdf |
99
+ | `pdf(x, w, k, lambda_, s, m, side=, method=, ...)` | pdf |
100
+ | `inv(p, w, k, lambda_, s, m, side=, method=, ...)` | inverse cdf |
101
+ | `gx2_to_norm_quad_params(w, k, lambda_, s, m)` | gx2 → quadratic-form coefficients of a standard normal |
102
+ | `norm_quad_to_gx2_params(mu, v, quad, merge=)` | quadratic form of a normal → gx2 parameters |
103
+
104
+ The individual computation routines (`imhof`, `ruben`, `ifft`, `pearson`,
105
+ `tail`, `ellipse`, `cdf_ray`, `pdf_ray`, …) and numerical helpers
106
+ (`log_sum_exp`, `signed_log_sum_exp`, `phi_ray`, …) are also exposed.
107
+
108
+ ## Computation methods for `cdf` / `pdf`
109
+
110
+ `method='auto'` (default) picks a good method for the given parameters. You can
111
+ also force one:
112
+
113
+ | method | notes |
114
+ |--------|-------|
115
+ | `'imhof'` | Imhof–Davies numerical integration (`precision='basic'` or `'vpa'`) |
116
+ | `'ray'` | ray-trace method (`precision='basic'`, `'log'` or `'vpa'`; tune with `n_rays`, `force_mc`) |
117
+ | `'ifft'` | inverse-FFT method; `x='full'` returns the cdf/pdf over a spanning grid |
118
+ | `'ruben'` | Ruben's series — requires all `w` the same sign and `s=0` |
119
+ | `'tail'` | infinite-tail approximation |
120
+ | `'pearson'` | Pearson's 3-moment approximation |
121
+ | `'ellipse'` | ellipse approximation near a finite tail — requires all `w` the same sign and `s=0` |
122
+
123
+ ## Usage notes
124
+
125
+ * `cdf` and `pdf` return just the probability/density by default. Pass
126
+ `full_output=True` (auto-enabled for `x='full'`) to also receive the error
127
+ estimate and, for `x='full'`, the grid of x-values.
128
+ * In the far tails, probabilities can fall below double precision (~1e-308).
129
+ The `'tail'`, `'ellipse'` and `'ray'` methods then return the **base-10
130
+ logarithm** of such values (a negative number); `inv` likewise accepts a
131
+ negative `p` as a log10 probability. `precision='log'` (the ray default) is
132
+ the easiest way to reach this regime.
133
+ * The `'ray'` method runs on the CPU with NumPy and batches automatically over
134
+ rays to bound memory. `precision='vpa'` uses `mpmath` and returns
135
+ `mpmath.mpf` objects for sub-`realmin` values.
136
+
137
+ ## Author and citation
138
+
139
+ Abhranil Das, Center for Perceptual Systems, The University of Texas at Austin.
140
+ Bugs / comments / questions / suggestions to abhranil.das@utexas.edu.
141
+
142
+ This is the Python port of the
143
+ [MATLAB toolbox](https://www.mathworks.com/matlabcentral/fileexchange/85028-generalized-chi-square-distribution).
144
+ If you use this code, please cite:
145
+ - [Methods to integrate multinormals and compute classification measures](https://arxiv.org/abs/2012.14331)
146
+ - New methods to compute the generalized chi-square distribution: [journal](https://www.tandfonline.com/doi/abs/10.1080/00949655.2025.2501401) / [arxiv](https://arxiv.org/abs/2404.05062)
147
+
148
+ ## License
149
+
150
+ MIT — see [LICENSE](LICENSE).
gx2-1.0.0/README.md ADDED
@@ -0,0 +1,116 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/abhranildas/gx2-py/main/gx2_icon.png" alt="gx2" width="260">
3
+ </p>
4
+
5
+ # gx2 — Generalized chi-square distribution
6
+
7
+ `gx2` computes the statistics, characteristic function, pdf, cdf, inverse cdf
8
+ and random numbers of the **generalized chi-square distribution**.
9
+
10
+ A generalized chi-square variable is a weighted sum of independent non-central
11
+ chi-square variables plus a normal variable — equivalently, the quadratic form
12
+ of a normal random vector. It is parametrized by:
13
+
14
+ | parameter | meaning |
15
+ |-----------|---------|
16
+ | `w` | weights of the non-central chi-square terms |
17
+ | `k` | their degrees of freedom |
18
+ | `lambda_` | their non-centralities (named `lambda_` because `lambda` is a Python keyword) |
19
+ | `s` | scale (standard deviation) of the added normal term |
20
+ | `m` | constant offset |
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install gx2
26
+ ```
27
+
28
+ Requires `numpy`, `scipy` and `mpmath`. `matplotlib` is optional, for plotting
29
+ in the getting-started notebook.
30
+
31
+ To install from a local clone instead:
32
+
33
+ ```bash
34
+ pip install .
35
+ # or, for development (editable install with test/plot extras):
36
+ pip install -e ".[plot,test]"
37
+ ```
38
+
39
+ ## Getting started
40
+
41
+ ```python
42
+ import gx2
43
+
44
+ w, k, lambda_, s, m = [1, -5, 2], [1, 2, 3], [2, 3, 7], 0, 5
45
+
46
+ gx2.stat(w, k, lambda_, s, m) # mean and variance
47
+ gx2.cdf(25, w, k, lambda_, s, m) # cdf at x = 25
48
+ gx2.pdf(25, w, k, lambda_, s, m) # pdf at x = 25
49
+ gx2.inv(0.9, w, k, lambda_, s, m) # 90th percentile
50
+ gx2.rnd(w, k, lambda_, s, m, size=5) # random numbers
51
+ ```
52
+
53
+ Open [`GettingStarted.ipynb`](GettingStarted.ipynb) for an interactive tour
54
+ with worked examples and plots. For any function, see its full documentation
55
+ with `help(gx2.cdf)` (or `gx2.cdf?` in Jupyter).
56
+
57
+ ## Public functions
58
+
59
+ | function | purpose |
60
+ |----------|---------|
61
+ | `stat(w, k, lambda_, s, m)` | mean and variance |
62
+ | `char(t, w, k, lambda_, s, m)` | characteristic function |
63
+ | `rnd(w, k, lambda_, s, m, size=, method=)` | random numbers |
64
+ | `cdf(x, w, k, lambda_, s, m, side=, method=, ...)` | cdf |
65
+ | `pdf(x, w, k, lambda_, s, m, side=, method=, ...)` | pdf |
66
+ | `inv(p, w, k, lambda_, s, m, side=, method=, ...)` | inverse cdf |
67
+ | `gx2_to_norm_quad_params(w, k, lambda_, s, m)` | gx2 → quadratic-form coefficients of a standard normal |
68
+ | `norm_quad_to_gx2_params(mu, v, quad, merge=)` | quadratic form of a normal → gx2 parameters |
69
+
70
+ The individual computation routines (`imhof`, `ruben`, `ifft`, `pearson`,
71
+ `tail`, `ellipse`, `cdf_ray`, `pdf_ray`, …) and numerical helpers
72
+ (`log_sum_exp`, `signed_log_sum_exp`, `phi_ray`, …) are also exposed.
73
+
74
+ ## Computation methods for `cdf` / `pdf`
75
+
76
+ `method='auto'` (default) picks a good method for the given parameters. You can
77
+ also force one:
78
+
79
+ | method | notes |
80
+ |--------|-------|
81
+ | `'imhof'` | Imhof–Davies numerical integration (`precision='basic'` or `'vpa'`) |
82
+ | `'ray'` | ray-trace method (`precision='basic'`, `'log'` or `'vpa'`; tune with `n_rays`, `force_mc`) |
83
+ | `'ifft'` | inverse-FFT method; `x='full'` returns the cdf/pdf over a spanning grid |
84
+ | `'ruben'` | Ruben's series — requires all `w` the same sign and `s=0` |
85
+ | `'tail'` | infinite-tail approximation |
86
+ | `'pearson'` | Pearson's 3-moment approximation |
87
+ | `'ellipse'` | ellipse approximation near a finite tail — requires all `w` the same sign and `s=0` |
88
+
89
+ ## Usage notes
90
+
91
+ * `cdf` and `pdf` return just the probability/density by default. Pass
92
+ `full_output=True` (auto-enabled for `x='full'`) to also receive the error
93
+ estimate and, for `x='full'`, the grid of x-values.
94
+ * In the far tails, probabilities can fall below double precision (~1e-308).
95
+ The `'tail'`, `'ellipse'` and `'ray'` methods then return the **base-10
96
+ logarithm** of such values (a negative number); `inv` likewise accepts a
97
+ negative `p` as a log10 probability. `precision='log'` (the ray default) is
98
+ the easiest way to reach this regime.
99
+ * The `'ray'` method runs on the CPU with NumPy and batches automatically over
100
+ rays to bound memory. `precision='vpa'` uses `mpmath` and returns
101
+ `mpmath.mpf` objects for sub-`realmin` values.
102
+
103
+ ## Author and citation
104
+
105
+ Abhranil Das, Center for Perceptual Systems, The University of Texas at Austin.
106
+ Bugs / comments / questions / suggestions to abhranil.das@utexas.edu.
107
+
108
+ This is the Python port of the
109
+ [MATLAB toolbox](https://www.mathworks.com/matlabcentral/fileexchange/85028-generalized-chi-square-distribution).
110
+ If you use this code, please cite:
111
+ - [Methods to integrate multinormals and compute classification measures](https://arxiv.org/abs/2012.14331)
112
+ - New methods to compute the generalized chi-square distribution: [journal](https://www.tandfonline.com/doi/abs/10.1080/00949655.2025.2501401) / [arxiv](https://arxiv.org/abs/2404.05062)
113
+
114
+ ## License
115
+
116
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,53 @@
1
+ """gx2 - Generalized chi-square distribution
2
+ ============================================
3
+
4
+ Python port of the MATLAB *Generalized chi-square distribution* toolbox by
5
+ Abhranil Das (Center for Perceptual Systems, The University of Texas at
6
+ Austin). It computes the statistics, characteristic function, pdf, cdf,
7
+ inverse cdf and random numbers of the generalized chi-square distribution --
8
+ the distribution of a weighted sum of non-central chi-square variables plus a
9
+ normal variable, equivalently the quadratic form of a normal vector.
10
+
11
+ A generalized chi-square is parametrised by:
12
+
13
+ w weights of the non-central chi-square terms
14
+ k their degrees of freedom
15
+ lambda_ their non-centralities (named ``lambda_`` since ``lambda`` is a
16
+ Python keyword)
17
+ s scale of the added normal term
18
+ m offset
19
+
20
+ If you use this code, please cite:
21
+ 1. A method to integrate and classify normal distributions
22
+ (https://arxiv.org/abs/2012.14331)
23
+ 2. New methods for computing the generalized chi-square distribution
24
+ (https://arxiv.org/abs/2404.05062)
25
+ """
26
+
27
+ from ._basic import stat, char, rnd
28
+ from ._convert import gx2_to_norm_quad_params, norm_quad_to_gx2_params
29
+ from ._distribution import cdf, pdf, inv, log_cdf
30
+ from ._methods import (imhof, imhof_integrand, ruben, ifft,
31
+ pearson, cdf_pearson, tail, ellipse)
32
+ from ._ray import (cdf_ray, pdf_ray, ray_integrand, int_norm_ray,
33
+ norm_prob_across_rays, norm_prob_across_angles)
34
+ from ._helpers import (log_sum_exp, signed_log_sum_exp, phi_ray,
35
+ Phibar_ray_split, Phibar_sym, prob_ray_sym, standard_quad)
36
+
37
+ __version__ = "1.0.0"
38
+
39
+ __all__ = [
40
+ # core distribution API
41
+ "stat", "char", "rnd", "cdf", "pdf", "inv", "log_cdf",
42
+ # parameter conversions
43
+ "gx2_to_norm_quad_params", "norm_quad_to_gx2_params",
44
+ # individual methods
45
+ "imhof", "imhof_integrand", "ruben", "ifft", "pearson",
46
+ "cdf_pearson", "tail", "ellipse",
47
+ # ray method internals
48
+ "cdf_ray", "pdf_ray", "ray_integrand", "int_norm_ray",
49
+ "norm_prob_across_rays", "norm_prob_across_angles",
50
+ # numerical helpers
51
+ "log_sum_exp", "signed_log_sum_exp", "phi_ray", "Phibar_ray_split",
52
+ "Phibar_sym", "prob_ray_sym", "standard_quad",
53
+ ]
@@ -0,0 +1,144 @@
1
+ """Mean/variance, characteristic function, and random number generation.
2
+ Mirrors ``gx2stat.m``, ``gx2char.m`` and ``gx2rnd.m``.
3
+ """
4
+
5
+ import numpy as np
6
+ from scipy.stats import ncx2, chi2, norm
7
+
8
+ from ._helpers import asrow
9
+ from ._convert import gx2_to_norm_quad_params
10
+
11
+
12
+ def stat(w, k, lambda_, s, m):
13
+ """Mean and variance of a generalized chi-square distribution.
14
+
15
+ Parameters
16
+ ----------
17
+ w : array_like
18
+ Weights of the non-central chi-square terms.
19
+ k : array_like
20
+ Degrees of freedom of the non-central chi-square terms.
21
+ lambda_ : array_like
22
+ Non-centrality parameters of the non-central chi-square terms.
23
+ s : float
24
+ Scale (standard deviation) of the added normal term.
25
+ m : float
26
+ Constant offset added to the distribution.
27
+
28
+ Returns
29
+ -------
30
+ mu : float
31
+ Mean.
32
+ v : float
33
+ Variance.
34
+ """
35
+ w = asrow(w)
36
+ k = asrow(k)
37
+ lambda_ = asrow(lambda_)
38
+ mu = float(np.dot(w, k + lambda_) + m)
39
+ v = float(2 * np.dot(w ** 2, k + 2 * lambda_) + s ** 2)
40
+ return mu, v
41
+
42
+
43
+ def char(t, w, k, lambda_, s, m):
44
+ """Characteristic function of a generalized chi-square distribution.
45
+
46
+ Parameters
47
+ ----------
48
+ t : array_like
49
+ Point(s) at which to evaluate the characteristic function.
50
+ w : array_like
51
+ Weights of the non-central chi-square terms.
52
+ k : array_like
53
+ Degrees of freedom of the non-central chi-square terms.
54
+ lambda_ : array_like
55
+ Non-centrality parameters of the non-central chi-square terms.
56
+ s : float
57
+ Scale (standard deviation) of the added normal term.
58
+ m : float
59
+ Constant offset added to the distribution.
60
+
61
+ Returns
62
+ -------
63
+ phi : ndarray of complex
64
+ The characteristic function at each ``t``, shaped like ``t``.
65
+ """
66
+ w = asrow(w)
67
+ k = asrow(k)
68
+ lambda_ = asrow(lambda_)
69
+ t = np.asarray(t, dtype=float)
70
+ tf = t.ravel()
71
+ tc = tf[:, None] # column
72
+
73
+ term = np.sum((w * lambda_) / (1 - 2j * tc * w), axis=1)
74
+ denom = np.prod((1 - 2j * w * tc) ** (k / 2), axis=1)
75
+ phi = np.exp(1j * m * tf + 1j * tf * term - s ** 2 * tf ** 2 / 2) / denom
76
+ return phi.reshape(t.shape)
77
+
78
+
79
+ def rnd(w, k, lambda_, s, m, size=None, method="sum"):
80
+ """Generate generalized chi-square random numbers.
81
+
82
+ Parameters
83
+ ----------
84
+ w : array_like
85
+ Weights of the non-central chi-square terms.
86
+ k : array_like
87
+ Degrees of freedom of the non-central chi-square terms.
88
+ lambda_ : array_like
89
+ Non-centrality parameters of the non-central chi-square terms.
90
+ s : float
91
+ Scale (standard deviation) of the added normal term.
92
+ m : float
93
+ Constant offset added to the distribution.
94
+ size : int or tuple, optional
95
+ Output shape. A scalar ``n`` gives an ``n x n`` array; a tuple gives
96
+ that exact shape. If omitted, a single scalar is returned.
97
+ method : {'sum', 'norm_quad'}
98
+ ``'sum'`` (default) generates non-central chi-square and normal numbers
99
+ and adds them. ``'norm_quad'`` generates standard normal vectors and
100
+ computes their quadratic form.
101
+
102
+ Returns
103
+ -------
104
+ r : float or ndarray
105
+ Random sample(s), of shape ``size`` (or a scalar if ``size`` is None).
106
+ """
107
+ w = asrow(w)
108
+ k = asrow(k)
109
+ lambda_ = asrow(lambda_)
110
+
111
+ if size is None:
112
+ shape = ()
113
+ elif np.isscalar(size):
114
+ shape = (int(size), int(size))
115
+ else:
116
+ shape = tuple(int(x) for x in size)
117
+
118
+ method = str(method).lower()
119
+ if method == "sum":
120
+ r = np.zeros(shape)
121
+ for wi, ki, li in zip(w, k, lambda_):
122
+ if li == 0:
123
+ r = r + wi * chi2.rvs(df=ki, size=shape)
124
+ else:
125
+ r = r + wi * ncx2.rvs(df=ki, nc=li, size=shape)
126
+ if s:
127
+ r = r + norm.rvs(loc=m, scale=s, size=shape)
128
+ else:
129
+ r = r + m
130
+ return r
131
+ elif method == "norm_quad":
132
+ quad = gx2_to_norm_quad_params(w, k, lambda_, s, m)
133
+ q1 = np.asarray(quad["q1"]).ravel()
134
+ q2 = np.asarray(quad["q2"])
135
+ q0 = quad["q0"]
136
+ dim = q1.size
137
+ n = int(np.prod(shape)) if shape else 1
138
+ z = norm.rvs(loc=0, scale=1, size=(dim, n))
139
+ r = np.sum(z * (q2 @ z), axis=0) + q1 @ z + q0
140
+ if shape:
141
+ return r.reshape(shape)
142
+ return float(r[0])
143
+ else:
144
+ raise ValueError("method must be 'sum' or 'norm_quad'")
@@ -0,0 +1,106 @@
1
+ """Conversions between generalized chi-square parameters and the quadratic form
2
+ of a normal vector. Mirrors ``gx2_to_norm_quad_params.m`` and
3
+ ``norm_quad_to_gx2_params.m``.
4
+ """
5
+
6
+ import numpy as np
7
+ from ._helpers import asrow, uniquetol
8
+
9
+
10
+ def gx2_to_norm_quad_params(w, k, lambda_, s, m):
11
+ """Quadratic-form coefficients of the standard normal whose quadratic form
12
+ is the given generalized chi-square.
13
+
14
+ Parameters
15
+ ----------
16
+ w, k, lambda_ : array_like
17
+ Weights, degrees of freedom and non-centralities of the non-central
18
+ chi-square terms.
19
+ s : float
20
+ Scale of the normal term.
21
+ m : float
22
+ Offset.
23
+
24
+ Returns
25
+ -------
26
+ quad : dict
27
+ ``{'q2': matrix, 'q1': vector, 'q0': scalar}``. The dimension of the
28
+ standard normal is ``len(q1)``.
29
+ """
30
+ w = asrow(w)
31
+ k = asrow(k)
32
+ lambda_ = asrow(lambda_)
33
+
34
+ q2_parts = []
35
+ q1_parts = []
36
+ for wi, ki, li in zip(w, k, lambda_):
37
+ ki = int(round(ki))
38
+ q2_parts.append(np.full(ki, wi))
39
+ q1_parts.append(np.concatenate(([wi * np.sqrt(li)], np.zeros(ki - 1))))
40
+ q2 = np.concatenate(q2_parts) if q2_parts else np.array([])
41
+ q1 = -2 * (np.concatenate(q1_parts) if q1_parts else np.array([]))
42
+
43
+ if s:
44
+ q2 = np.append(q2, 0.0)
45
+ q1 = np.append(q1, s)
46
+
47
+ return {"q2": np.diag(q2), "q1": q1.astype(float), "q0": float(np.dot(w, lambda_) + m)}
48
+
49
+
50
+ def norm_quad_to_gx2_params(mu, v, quad, merge=True):
51
+ """Parameters of the generalized chi-square distribution of a quadratic
52
+ form ``q(x) = x' q2 x + q1' x + q0`` of a normal vector ``x ~ N(mu, v)``.
53
+
54
+ Parameters
55
+ ----------
56
+ mu : array_like
57
+ Column vector of the normal mean.
58
+ v : array_like
59
+ Normal covariance matrix.
60
+ quad : dict
61
+ ``{'q2': matrix, 'q1': vector, 'q0': scalar}``.
62
+ merge : bool, optional
63
+ If True (default), merge non-central chi-square components with
64
+ close-enough weights into single components. Set False to return all
65
+ raw exact components.
66
+
67
+ Returns
68
+ -------
69
+ w, k, lambda_, s, m
70
+ """
71
+ mu = np.asarray(mu, dtype=float).ravel()
72
+ v = np.asarray(v, dtype=float)
73
+ q2_in = np.asarray(quad["q2"], dtype=float)
74
+ q1_in = np.asarray(quad["q1"], dtype=float).ravel()
75
+ q0_in = float(quad["q0"])
76
+
77
+ q2_sym = 0.5 * (q2_in + q2_in.T)
78
+
79
+ # sqrtm(v) avoiding small negative eigenvalues
80
+ d, R = np.linalg.eigh(v)
81
+ d = np.where(d < 0, 0.0, d)
82
+ sqrt_v = R @ np.diag(np.sqrt(d)) @ R.T
83
+
84
+ q2 = sqrt_v @ q2_sym @ sqrt_v
85
+ q2 = (q2 + q2.T) / 2
86
+ q1 = sqrt_v @ (2 * q2_sym @ mu + q1_in)
87
+ q0 = float(mu @ q2_sym @ mu + q1_in @ mu + q0_in)
88
+
89
+ d2, R2 = np.linalg.eigh(q2)
90
+ d = d2
91
+ b = (R2.T @ q1)
92
+
93
+ nz = d != 0
94
+ if merge:
95
+ w, ic = uniquetol(d[nz])
96
+ k = np.bincount(ic, minlength=w.size).astype(float)
97
+ b_sq_sum = np.bincount(ic, weights=b[nz] ** 2, minlength=w.size)
98
+ lambda_ = b_sq_sum / (4 * w ** 2)
99
+ else:
100
+ w = d[nz].copy()
101
+ k = np.ones(w.size)
102
+ lambda_ = b[nz] ** 2 / (4 * w ** 2)
103
+
104
+ m = q0 - np.dot(w, lambda_)
105
+ s = np.linalg.norm(b[~nz])
106
+ return w, k, lambda_, s, m