fue 0.1.0__py3-none-any.whl
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.
- fue/__init__.py +32 -0
- fue/__main__.py +3 -0
- fue/_build_cffi.py +174 -0
- fue/_engine.py +130 -0
- fue/cast_us.py +600 -0
- fue/cli.py +158 -0
- fue/datasets.py +75 -0
- fue/diagnostics.py +82 -0
- fue/elfvarma.py +710 -0
- fue/forecast.py +549 -0
- fue/fuf_cli.py +135 -0
- fue/inp.py +508 -0
- fue/intervention.py +70 -0
- fue/model.py +422 -0
- fue/plots.py +602 -0
- fue/qnewtopt.py +393 -0
- fue/report.py +1605 -0
- fue/report_forecast.py +663 -0
- fue/series.py +202 -0
- fue-0.1.0.dist-info/METADATA +131 -0
- fue-0.1.0.dist-info/RECORD +24 -0
- fue-0.1.0.dist-info/WHEEL +5 -0
- fue-0.1.0.dist-info/entry_points.txt +3 -0
- fue-0.1.0.dist-info/top_level.txt +2 -0
fue/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fue — Python interface to the FUE exact maximum likelihood estimation engine.
|
|
3
|
+
|
|
4
|
+
Typical usage::
|
|
5
|
+
|
|
6
|
+
import fue
|
|
7
|
+
|
|
8
|
+
ts = fue.TimeSeries.from_array(data, freq=12, start=(1990, 1))
|
|
9
|
+
|
|
10
|
+
m = fue.Model(ts, ar=[[1]], ma=[[1]], d=1, D=1)
|
|
11
|
+
m.fit()
|
|
12
|
+
print(m.summary())
|
|
13
|
+
|
|
14
|
+
m.plot_residuals()
|
|
15
|
+
m.residuals.plot_acf()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .series import TimeSeries
|
|
19
|
+
from .intervention import Intervention
|
|
20
|
+
from .model import Model, FixedFreqFactor
|
|
21
|
+
from .forecast import ForecastResult
|
|
22
|
+
from .diagnostics import acf, pacf, jarque_bera, ljung_box
|
|
23
|
+
from .inp import load, load_fuf
|
|
24
|
+
from .report import write_out, write_fuf, write_fuf_out
|
|
25
|
+
from .report_forecast import write_forecast_report
|
|
26
|
+
from . import datasets
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
__all__ = ["TimeSeries", "Intervention", "Model", "FixedFreqFactor",
|
|
30
|
+
"ForecastResult", "acf", "pacf", "jarque_bera", "ljung_box",
|
|
31
|
+
"load", "load_fuf", "write_out", "write_fuf", "write_fuf_out",
|
|
32
|
+
"write_forecast_report", "datasets"]
|
fue/__main__.py
ADDED
fue/_build_cffi.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cffi build script for the FUE estimation engine.
|
|
3
|
+
|
|
4
|
+
Activated in pyproject.toml via:
|
|
5
|
+
cffi_modules = ["src/fue/_build_cffi.py:ffi"]
|
|
6
|
+
|
|
7
|
+
This file is run at build time (pip install / python setup.py build_ext).
|
|
8
|
+
It compiles csrc/fue_api.c + csrc/internal/*.c against GSL and produces
|
|
9
|
+
the _fue_engine extension module imported by _engine.py at runtime.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import cffi
|
|
15
|
+
|
|
16
|
+
# Compute paths relative to the project root (CWD during pip/setup.py build).
|
|
17
|
+
# os.path.relpath normalises away the ".." components that confuse distutils.
|
|
18
|
+
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
19
|
+
_ROOT = os.path.relpath(os.path.join(_THIS_DIR, "..", "..", "csrc"))
|
|
20
|
+
_INTERN = os.path.join(_ROOT, "internal")
|
|
21
|
+
|
|
22
|
+
# cffi cannot process C preprocessor directives (#define, #ifdef, etc.).
|
|
23
|
+
# We provide a macro-expanded cdef string with literal values instead.
|
|
24
|
+
# Macro values: FUE_MAX_DETVARS=64, FUE_MAX_FACTORS=8, FUE_MAX_POLYORD=16.
|
|
25
|
+
_CDEF = """
|
|
26
|
+
typedef struct {
|
|
27
|
+
int type;
|
|
28
|
+
int obs_index;
|
|
29
|
+
double harmonic;
|
|
30
|
+
|
|
31
|
+
int nomega;
|
|
32
|
+
double omega[16];
|
|
33
|
+
int omega_free[16];
|
|
34
|
+
|
|
35
|
+
int ndelta;
|
|
36
|
+
double delta[16];
|
|
37
|
+
int delta_free[16];
|
|
38
|
+
|
|
39
|
+
double *indicator_data;
|
|
40
|
+
} FueIntervention;
|
|
41
|
+
|
|
42
|
+
typedef struct {
|
|
43
|
+
int order;
|
|
44
|
+
double coefs[16];
|
|
45
|
+
int coef_free[16];
|
|
46
|
+
} FueFactor;
|
|
47
|
+
|
|
48
|
+
typedef struct {
|
|
49
|
+
int nobs;
|
|
50
|
+
double *data;
|
|
51
|
+
int sper;
|
|
52
|
+
int numbering;
|
|
53
|
+
int begyear;
|
|
54
|
+
int begtime;
|
|
55
|
+
|
|
56
|
+
double boxlam;
|
|
57
|
+
double refactor;
|
|
58
|
+
|
|
59
|
+
int nrdiff;
|
|
60
|
+
int nadiff;
|
|
61
|
+
|
|
62
|
+
double mu0;
|
|
63
|
+
int estimate_mu;
|
|
64
|
+
|
|
65
|
+
int ninterventions;
|
|
66
|
+
FueIntervention interventions[64];
|
|
67
|
+
|
|
68
|
+
int nar1;
|
|
69
|
+
FueFactor ar1[8];
|
|
70
|
+
|
|
71
|
+
int nar2;
|
|
72
|
+
FueFactor ar2[8];
|
|
73
|
+
|
|
74
|
+
int nma1;
|
|
75
|
+
FueFactor ma1[8];
|
|
76
|
+
|
|
77
|
+
int nma2;
|
|
78
|
+
FueFactor ma2[8];
|
|
79
|
+
|
|
80
|
+
int nar1f;
|
|
81
|
+
double ar1f_freq[8];
|
|
82
|
+
double ar1f_coef[8];
|
|
83
|
+
int ar1f_free[8];
|
|
84
|
+
|
|
85
|
+
int nma1f;
|
|
86
|
+
double ma1f_freq[8];
|
|
87
|
+
double ma1f_coef[8];
|
|
88
|
+
int ma1f_free[8];
|
|
89
|
+
|
|
90
|
+
int ifadf[8];
|
|
91
|
+
|
|
92
|
+
int maxits;
|
|
93
|
+
double grtol;
|
|
94
|
+
double sptol;
|
|
95
|
+
double xitol;
|
|
96
|
+
int chkma;
|
|
97
|
+
int eml;
|
|
98
|
+
} FueModelSpec;
|
|
99
|
+
|
|
100
|
+
typedef struct {
|
|
101
|
+
int ifault;
|
|
102
|
+
int npar;
|
|
103
|
+
double *params;
|
|
104
|
+
double *std_errors;
|
|
105
|
+
double *cov_matrix;
|
|
106
|
+
double *residuals;
|
|
107
|
+
int nresiduals;
|
|
108
|
+
double sigma2;
|
|
109
|
+
double loglik;
|
|
110
|
+
double aic;
|
|
111
|
+
double bic;
|
|
112
|
+
} FueResult;
|
|
113
|
+
|
|
114
|
+
FueResult *fue_estimate(const FueModelSpec *spec);
|
|
115
|
+
void fue_defaults(FueModelSpec *spec);
|
|
116
|
+
void fue_result_free(FueResult *r);
|
|
117
|
+
const char *fue_strerror(int ifault);
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
_SOURCES = [
|
|
121
|
+
os.path.join(_ROOT, "fue_api.c"),
|
|
122
|
+
os.path.join(_INTERN, "elfvarma.c"),
|
|
123
|
+
os.path.join(_INTERN, "usmelard.c"),
|
|
124
|
+
os.path.join(_INTERN, "qnewtopt.c"),
|
|
125
|
+
os.path.join(_INTERN, "nlatools.c"),
|
|
126
|
+
os.path.join(_INTERN, "drvmlest.c"),
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
if sys.platform == "win32":
|
|
130
|
+
_libraries = ["gsl", "gslcblas"] # no -lm on Windows
|
|
131
|
+
_compile_args = ["/O2", "/W2"]
|
|
132
|
+
# conda-build sets LIBRARY_INC / LIBRARY_LIB; prefer those when present.
|
|
133
|
+
_lib_inc = os.environ.get("LIBRARY_INC")
|
|
134
|
+
_lib_lib = os.environ.get("LIBRARY_LIB")
|
|
135
|
+
if _lib_inc and _lib_lib:
|
|
136
|
+
_include_dirs = [_ROOT, _INTERN, _lib_inc]
|
|
137
|
+
_library_dirs = [_lib_lib]
|
|
138
|
+
else:
|
|
139
|
+
# GSL via vcpkg (x64-windows-static-md triplet) — used by cibuildwheel.
|
|
140
|
+
# Set GSL_ROOT to override the default vcpkg install prefix.
|
|
141
|
+
_vcpkg_default = os.path.join(
|
|
142
|
+
os.environ.get("VCPKG_INSTALLATION_ROOT", r"C:\vcpkg"),
|
|
143
|
+
"installed", "x64-windows-static-md",
|
|
144
|
+
)
|
|
145
|
+
_gsl_root = os.environ.get("GSL_ROOT", _vcpkg_default)
|
|
146
|
+
_include_dirs = [_ROOT, _INTERN, os.path.join(_gsl_root, "include")]
|
|
147
|
+
_library_dirs = [os.path.join(_gsl_root, "lib")]
|
|
148
|
+
else:
|
|
149
|
+
_libraries = ["gsl", "gslcblas", "m"]
|
|
150
|
+
_compile_args = ["-O2", "-std=c99", "-Wall"]
|
|
151
|
+
# conda-build sets PREFIX to the environment where host deps are installed.
|
|
152
|
+
_prefix = os.environ.get("PREFIX")
|
|
153
|
+
if _prefix:
|
|
154
|
+
_include_dirs = [_ROOT, _INTERN, os.path.join(_prefix, "include")]
|
|
155
|
+
_library_dirs = [os.path.join(_prefix, "lib")]
|
|
156
|
+
else:
|
|
157
|
+
_include_dirs = [_ROOT, _INTERN]
|
|
158
|
+
_library_dirs = []
|
|
159
|
+
|
|
160
|
+
ffi = cffi.FFI()
|
|
161
|
+
ffi.cdef(_CDEF)
|
|
162
|
+
|
|
163
|
+
ffi.set_source(
|
|
164
|
+
"fue._fue_engine",
|
|
165
|
+
r'#include "fue_api.h"',
|
|
166
|
+
sources=_SOURCES,
|
|
167
|
+
include_dirs=_include_dirs,
|
|
168
|
+
library_dirs=_library_dirs,
|
|
169
|
+
libraries=_libraries,
|
|
170
|
+
extra_compile_args=_compile_args,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
ffi.compile(verbose=True)
|
fue/_engine.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bridge between the Python Model API and the cffi-compiled C extension.
|
|
3
|
+
|
|
4
|
+
Falls back to the pure-Python estimator (cast_us.estimate_py) when the
|
|
5
|
+
C extension (_fue_engine) is not available.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def estimate(model):
|
|
12
|
+
"""
|
|
13
|
+
Estimate model parameters by exact ML.
|
|
14
|
+
|
|
15
|
+
Tries the C extension first; falls back to the pure-Python estimator
|
|
16
|
+
(scipy L-BFGS-B + elf_scalar) when the extension is not compiled.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
from fue._fue_engine import ffi, lib
|
|
20
|
+
except ImportError:
|
|
21
|
+
from .cast_us import estimate_py
|
|
22
|
+
return estimate_py(model)
|
|
23
|
+
|
|
24
|
+
spec = ffi.new("FueModelSpec *")
|
|
25
|
+
lib.fue_defaults(spec)
|
|
26
|
+
|
|
27
|
+
ts = model.series
|
|
28
|
+
|
|
29
|
+
# Keep the numpy array alive via ffi.from_buffer so spec.data stays valid.
|
|
30
|
+
_data = np.ascontiguousarray(ts.data, dtype=np.float64)
|
|
31
|
+
_data_buf = ffi.from_buffer("double[]", _data)
|
|
32
|
+
|
|
33
|
+
spec.nobs = ts.nobs
|
|
34
|
+
spec.data = _data_buf
|
|
35
|
+
spec.sper = ts.freq if ts.freq > 0 else 1
|
|
36
|
+
spec.numbering = 1 if ts.numbering else 0
|
|
37
|
+
spec.begyear = ts.start[0]
|
|
38
|
+
spec.begtime = ts.start[1]
|
|
39
|
+
|
|
40
|
+
spec.boxlam = model.boxlam
|
|
41
|
+
spec.refactor = model.refactor
|
|
42
|
+
spec.nrdiff = model.d
|
|
43
|
+
spec.nadiff = model.D
|
|
44
|
+
spec.mu0 = model.mu0
|
|
45
|
+
spec.estimate_mu = 1 if model.estimate_mu else 0
|
|
46
|
+
spec.chkma = 1 if model.chkma else 0
|
|
47
|
+
spec.eml = 1 if model.eml else 0
|
|
48
|
+
|
|
49
|
+
def _fill_factors(spec_arr, factors, free_lists):
|
|
50
|
+
for i, factor in enumerate(factors):
|
|
51
|
+
spec_arr[i].order = len(factor)
|
|
52
|
+
free = free_lists[i] if free_lists is not None else None
|
|
53
|
+
for j, v in enumerate(factor):
|
|
54
|
+
spec_arr[i].coefs[j] = v
|
|
55
|
+
spec_arr[i].coef_free[j] = (
|
|
56
|
+
0 if (free is not None and not free[j]) else 1
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
spec.nar1 = len(model.ar); _fill_factors(spec.ar1, model.ar, model.ar_free)
|
|
60
|
+
spec.nma1 = len(model.ma); _fill_factors(spec.ma1, model.ma, model.ma_free)
|
|
61
|
+
spec.nar2 = len(model.ar_s); _fill_factors(spec.ar2, model.ar_s, model.ar_s_free)
|
|
62
|
+
spec.nma2 = len(model.ma_s); _fill_factors(spec.ma2, model.ma_s, model.ma_s_free)
|
|
63
|
+
|
|
64
|
+
for i, v in enumerate(model.ifadf[:8]):
|
|
65
|
+
spec.ifadf[i] = 1 if v else 0
|
|
66
|
+
|
|
67
|
+
spec.nar1f = len(model.ar_f)
|
|
68
|
+
for i, ff in enumerate(model.ar_f):
|
|
69
|
+
spec.ar1f_freq[i] = ff.freq
|
|
70
|
+
spec.ar1f_coef[i] = ff.coef
|
|
71
|
+
spec.ar1f_free[i] = 1 if ff.free else 0
|
|
72
|
+
|
|
73
|
+
spec.nma1f = len(model.ma_f)
|
|
74
|
+
for i, ff in enumerate(model.ma_f):
|
|
75
|
+
spec.ma1f_freq[i] = ff.freq
|
|
76
|
+
spec.ma1f_coef[i] = ff.coef
|
|
77
|
+
spec.ma1f_free[i] = 1 if ff.free else 0
|
|
78
|
+
|
|
79
|
+
spec.ninterventions = len(model.interventions)
|
|
80
|
+
# Custom indicator buffers must stay alive until fue_estimate returns.
|
|
81
|
+
_custom_bufs = []
|
|
82
|
+
for i, itv in enumerate(model.interventions):
|
|
83
|
+
spec.interventions[i].type = itv.type_code
|
|
84
|
+
spec.interventions[i].obs_index = itv.at
|
|
85
|
+
spec.interventions[i].harmonic = itv.harmonic
|
|
86
|
+
spec.interventions[i].nomega = len(itv.omega)
|
|
87
|
+
for j, v in enumerate(itv.omega):
|
|
88
|
+
spec.interventions[i].omega[j] = v
|
|
89
|
+
spec.interventions[i].omega_free[j] = 1 if itv.omega_free[j] else 0
|
|
90
|
+
spec.interventions[i].ndelta = len(itv.delta)
|
|
91
|
+
for j, v in enumerate(itv.delta):
|
|
92
|
+
spec.interventions[i].delta[j] = v
|
|
93
|
+
spec.interventions[i].delta_free[j] = 1 if itv.delta_free[j] else 0
|
|
94
|
+
if itv.type == "custom" and itv.data is not None:
|
|
95
|
+
_arr = np.ascontiguousarray(itv.data, dtype=np.float64)
|
|
96
|
+
_buf = ffi.from_buffer("double[]", _arr)
|
|
97
|
+
spec.interventions[i].indicator_data = _buf
|
|
98
|
+
_custom_bufs.append((_arr, _buf))
|
|
99
|
+
else:
|
|
100
|
+
spec.interventions[i].indicator_data = ffi.NULL
|
|
101
|
+
|
|
102
|
+
# _data, _data_buf, and _custom_bufs remain alive until fue_estimate returns.
|
|
103
|
+
raw = lib.fue_estimate(spec)
|
|
104
|
+
if raw == ffi.NULL:
|
|
105
|
+
return {'ifault': -1, 'npar': 0, 'nresiduals': 0,
|
|
106
|
+
'sigma2': 0.0, 'loglik': 0.0, 'aic': 0.0, 'bic': 0.0,
|
|
107
|
+
'params': np.array([]), 'std_errors': np.array([]),
|
|
108
|
+
'cov_matrix': np.zeros((0, 0)), 'residuals': np.array([])}
|
|
109
|
+
|
|
110
|
+
n = raw.npar
|
|
111
|
+
nr = raw.nresiduals
|
|
112
|
+
try:
|
|
113
|
+
data = {
|
|
114
|
+
'ifault': raw.ifault,
|
|
115
|
+
'npar': n,
|
|
116
|
+
'nresiduals': nr,
|
|
117
|
+
'sigma2': raw.sigma2,
|
|
118
|
+
'loglik': raw.loglik,
|
|
119
|
+
'aic': raw.aic,
|
|
120
|
+
'bic': raw.bic,
|
|
121
|
+
'params': np.array([raw.params[i] for i in range(n)], dtype=float),
|
|
122
|
+
'std_errors': np.array([raw.std_errors[i] for i in range(n)], dtype=float),
|
|
123
|
+
'cov_matrix': np.array([raw.cov_matrix[i] for i in range(n * n)],
|
|
124
|
+
dtype=float).reshape(n, n) if n > 0 else np.zeros((0, 0)),
|
|
125
|
+
'residuals': np.array([raw.residuals[i] for i in range(nr)], dtype=float),
|
|
126
|
+
}
|
|
127
|
+
finally:
|
|
128
|
+
lib.fue_result_free(raw)
|
|
129
|
+
|
|
130
|
+
return data
|