flowfreq 0.8.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.
- flowfreq/__init__.py +272 -0
- flowfreq/_detrat.py +202 -0
- flowfreq/_mse_ema.py +273 -0
- flowfreq/_p3_moments.py +399 -0
- flowfreq/_var_emab.py +276 -0
- flowfreq/_var_mom.py +309 -0
- flowfreq/batch.py +129 -0
- flowfreq/bulletin17c.py +2076 -0
- flowfreq/cli.py +157 -0
- flowfreq/core.py +474 -0
- flowfreq/data/gage_attributes.csv +4 -0
- flowfreq/engine.py +231 -0
- flowfreq/flowio.py +133 -0
- flowfreq/fortran_engine.py +597 -0
- flowfreq/freq_plot.py +669 -0
- flowfreq/hydrograph.py +380 -0
- flowfreq/lowflow.py +721 -0
- flowfreq/peakfqr/__init__.py +41 -0
- flowfreq/plots.py +255 -0
- flowfreq/py.typed +0 -0
- flowfreq/qppq.py +937 -0
- flowfreq/regime.py +1266 -0
- flowfreq/report.py +253 -0
- flowfreq/streamstats.py +1314 -0
- flowfreq/transpose.py +1319 -0
- flowfreq/usgs.py +1085 -0
- flowfreq/validation/__init__.py +22 -0
- flowfreq/validation/benchmarks.py +300 -0
- flowfreq/validation/comparisons.py +317 -0
- flowfreq/validation/data/big_sandy_03606500.json +157 -0
- flowfreq/validation/reference.py +352 -0
- flowfreq/validation/reports.py +88 -0
- flowfreq/workflow.py +574 -0
- flowfreq-0.8.0.dist-info/METADATA +344 -0
- flowfreq-0.8.0.dist-info/RECORD +39 -0
- flowfreq-0.8.0.dist-info/WHEEL +5 -0
- flowfreq-0.8.0.dist-info/entry_points.txt +2 -0
- flowfreq-0.8.0.dist-info/licenses/LICENSE +21 -0
- flowfreq-0.8.0.dist-info/top_level.txt +1 -0
flowfreq/__init__.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
flowfreq - Python library for hydrologic analysis
|
|
3
|
+
|
|
4
|
+
Includes:
|
|
5
|
+
- USGS gage data download (daily and peak flows)
|
|
6
|
+
- Summary hydrograph plotting
|
|
7
|
+
- Bulletin 17C flood frequency analysis:
|
|
8
|
+
- Method of Moments (MOM)
|
|
9
|
+
- Expected Moments Algorithm (EMA) with full PeakFQ parity
|
|
10
|
+
- Historical flood information handling
|
|
11
|
+
- Technical report generation
|
|
12
|
+
- One-call analysis workflow (flowfreq.workflow.run_ffa)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from importlib.metadata import PackageNotFoundError
|
|
17
|
+
from importlib.metadata import version as _installed_version
|
|
18
|
+
|
|
19
|
+
from .bulletin17c import (
|
|
20
|
+
Bulletin17C,
|
|
21
|
+
ExpectedMomentsAlgorithm,
|
|
22
|
+
FloodFrequencyAnalysis,
|
|
23
|
+
MethodOfMoments,
|
|
24
|
+
)
|
|
25
|
+
from .core import (
|
|
26
|
+
AnalysisMethod,
|
|
27
|
+
EMAParameters,
|
|
28
|
+
FlowInterval,
|
|
29
|
+
FrequencyResults,
|
|
30
|
+
LowFlowResults,
|
|
31
|
+
SkewMethod,
|
|
32
|
+
grubbs_beck_critical_value,
|
|
33
|
+
kfactor,
|
|
34
|
+
kfactor_array,
|
|
35
|
+
)
|
|
36
|
+
from .flowio import load_flow_frame, save_flow_frame
|
|
37
|
+
from .hydrograph import Hydrograph
|
|
38
|
+
from .lowflow import LOW_FLOW_YEAR_TYPES, LowFlowFrequency, annual_minimum_flow
|
|
39
|
+
from .qppq import (
|
|
40
|
+
MONTHLY_SEASONS,
|
|
41
|
+
SNOWMELT_SEASONS,
|
|
42
|
+
FlowDurationCurve,
|
|
43
|
+
QppqResult,
|
|
44
|
+
apply_lag,
|
|
45
|
+
center_of_timing,
|
|
46
|
+
estimate_donor_lag,
|
|
47
|
+
loocv_qppq,
|
|
48
|
+
performance,
|
|
49
|
+
qppq,
|
|
50
|
+
rank_donors,
|
|
51
|
+
seasonal_curves,
|
|
52
|
+
)
|
|
53
|
+
from .regime import (
|
|
54
|
+
BASEFLOW_METHODS,
|
|
55
|
+
DEFAULT_EXCEEDANCE_PCT,
|
|
56
|
+
FlowRegime,
|
|
57
|
+
baseflow_index,
|
|
58
|
+
diel_variation,
|
|
59
|
+
diel_variation_summary,
|
|
60
|
+
flow_duration_curve,
|
|
61
|
+
monthly_flow_summary,
|
|
62
|
+
richards_baker_flashiness,
|
|
63
|
+
seasonal_flow_summary,
|
|
64
|
+
separate_baseflow,
|
|
65
|
+
tqmean,
|
|
66
|
+
)
|
|
67
|
+
from .report import HydroReport
|
|
68
|
+
from .transpose import (
|
|
69
|
+
DEFAULT_AREA_RATIO_RANGE,
|
|
70
|
+
LOW_FLOW_AREA_RATIO_RANGE,
|
|
71
|
+
PROBABILITY_KINDS,
|
|
72
|
+
RegressionExponents,
|
|
73
|
+
TransposedResults,
|
|
74
|
+
TranspositionProvenance,
|
|
75
|
+
transpose_duration,
|
|
76
|
+
transpose_frequency,
|
|
77
|
+
transpose_low_flow,
|
|
78
|
+
)
|
|
79
|
+
from .usgs import (
|
|
80
|
+
GageAttributes,
|
|
81
|
+
NoInstantaneousDataError,
|
|
82
|
+
USGSgage,
|
|
83
|
+
fetch_nwis_batch,
|
|
84
|
+
fetch_nwis_peaks,
|
|
85
|
+
)
|
|
86
|
+
from .workflow import (
|
|
87
|
+
B17C_DEFAULT_SKEW,
|
|
88
|
+
DEFAULT_AEP,
|
|
89
|
+
DEFAULT_RETURN_INTERVALS,
|
|
90
|
+
SKEW_OPTIONS,
|
|
91
|
+
build_skew_curves_dict,
|
|
92
|
+
compute_skew_tables,
|
|
93
|
+
run_ffa,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Alias for backwards compatibility
|
|
97
|
+
USGSGage = USGSgage
|
|
98
|
+
|
|
99
|
+
logger = logging.getLogger(__name__)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def analyze_gage(
|
|
103
|
+
site_no: str,
|
|
104
|
+
method: str = "ema",
|
|
105
|
+
regional_skew: float = None,
|
|
106
|
+
regional_skew_mse: float = None,
|
|
107
|
+
historical_peaks: list = None,
|
|
108
|
+
output_dir: str = "./output",
|
|
109
|
+
) -> dict:
|
|
110
|
+
"""
|
|
111
|
+
Complete flood frequency analysis for a USGS gage.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
site_no : str
|
|
116
|
+
USGS site number
|
|
117
|
+
method : str
|
|
118
|
+
'mom' or 'ema' (default: 'ema')
|
|
119
|
+
regional_skew : float, optional
|
|
120
|
+
Regional skew coefficient
|
|
121
|
+
regional_skew_mse : float, optional
|
|
122
|
+
Mean squared error of regional skew
|
|
123
|
+
historical_peaks : list of (year, flow) tuples, optional
|
|
124
|
+
Historical peak observations
|
|
125
|
+
output_dir : str
|
|
126
|
+
Output directory
|
|
127
|
+
"""
|
|
128
|
+
import os
|
|
129
|
+
|
|
130
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
131
|
+
|
|
132
|
+
logger.info("Downloading data for USGS %s...", site_no)
|
|
133
|
+
gage = USGSgage(site_no)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
gage.download_daily_flow()
|
|
137
|
+
logger.info("Downloaded %d days of daily flow data", len(gage.daily_data))
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.warning("Could not download daily flow data: %s", e)
|
|
140
|
+
|
|
141
|
+
gage.download_peak_flow()
|
|
142
|
+
logger.info("Downloaded %d annual peak flow records", len(gage.peak_data))
|
|
143
|
+
logger.info("Site name: %s", gage.site_name)
|
|
144
|
+
|
|
145
|
+
logger.info("Running Bulletin 17C analysis (method=%s)...", method.upper())
|
|
146
|
+
|
|
147
|
+
water_years = gage.peak_data["water_year"].values
|
|
148
|
+
|
|
149
|
+
analysis = Bulletin17C(
|
|
150
|
+
gage.peak_data["peak_flow_cfs"].values,
|
|
151
|
+
water_years=water_years,
|
|
152
|
+
regional_skew=regional_skew,
|
|
153
|
+
regional_skew_mse=regional_skew_mse,
|
|
154
|
+
historical_peaks=historical_peaks,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
results = analysis.run_analysis(method=method)
|
|
158
|
+
|
|
159
|
+
logger.info("Station skew: %.4f", results.skew_station)
|
|
160
|
+
if results.skew_weighted is not None:
|
|
161
|
+
logger.info("Weighted skew: %.4f", results.skew_weighted)
|
|
162
|
+
logger.info("Low outlier threshold: %s cfs", f"{results.low_outlier_threshold:,.0f}")
|
|
163
|
+
|
|
164
|
+
if results.method == AnalysisMethod.EMA:
|
|
165
|
+
logger.info("EMA iterations: %s", results.ema_iterations)
|
|
166
|
+
logger.info("EMA converged: %s", results.ema_converged)
|
|
167
|
+
|
|
168
|
+
logger.info("Generating report and figures...")
|
|
169
|
+
report = HydroReport(gage, analysis)
|
|
170
|
+
figures = report.generate_all_figures(output_dir)
|
|
171
|
+
|
|
172
|
+
report_path = os.path.join(output_dir, "flood_frequency_report.md")
|
|
173
|
+
report.save_report(report_path)
|
|
174
|
+
logger.info("Report saved to: %s", report_path)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
"gage": gage,
|
|
178
|
+
"analysis": analysis,
|
|
179
|
+
"results": results,
|
|
180
|
+
"figures": figures,
|
|
181
|
+
"report_path": report_path,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Read from the installed distribution rather than repeated here as a
|
|
186
|
+
# literal. The literal drifted every time: it read "0.3.0" through the 0.4.0
|
|
187
|
+
# release (docs/PHASE1_RUNBOOK.md still records it saying so) and "0.4.0"
|
|
188
|
+
# through 0.5.0, 0.6.0 and 0.6.1, so `flowfreq.__version__` has spent most of
|
|
189
|
+
# this project's life reporting a version that was not the one installed.
|
|
190
|
+
# pyproject.toml is the single source of truth; this reads what pip actually
|
|
191
|
+
# installed from it, which cannot disagree.
|
|
192
|
+
try:
|
|
193
|
+
__version__ = _installed_version("flowfreq")
|
|
194
|
+
except PackageNotFoundError: # pragma: no cover - only in an uninstalled checkout
|
|
195
|
+
# No dist-info: someone is importing from a source tree they never
|
|
196
|
+
# installed. Saying so is better than naming a version that may be wrong.
|
|
197
|
+
__version__ = "unknown"
|
|
198
|
+
|
|
199
|
+
__author__ = "FlowFreq"
|
|
200
|
+
|
|
201
|
+
__all__ = [
|
|
202
|
+
# Core
|
|
203
|
+
"AnalysisMethod",
|
|
204
|
+
"SkewMethod",
|
|
205
|
+
"FlowInterval",
|
|
206
|
+
"EMAParameters",
|
|
207
|
+
"FrequencyResults",
|
|
208
|
+
"kfactor",
|
|
209
|
+
"kfactor_array",
|
|
210
|
+
"grubbs_beck_critical_value",
|
|
211
|
+
# QPPQ daily-series transfer
|
|
212
|
+
"FlowDurationCurve",
|
|
213
|
+
"QppqResult",
|
|
214
|
+
"qppq",
|
|
215
|
+
"seasonal_curves",
|
|
216
|
+
"center_of_timing",
|
|
217
|
+
"estimate_donor_lag",
|
|
218
|
+
"apply_lag",
|
|
219
|
+
"rank_donors",
|
|
220
|
+
"performance",
|
|
221
|
+
"loocv_qppq",
|
|
222
|
+
"SNOWMELT_SEASONS",
|
|
223
|
+
"MONTHLY_SEASONS",
|
|
224
|
+
# Transposition to an ungaged site
|
|
225
|
+
"RegressionExponents",
|
|
226
|
+
"TransposedResults",
|
|
227
|
+
"TranspositionProvenance",
|
|
228
|
+
"transpose_frequency",
|
|
229
|
+
"transpose_duration",
|
|
230
|
+
"transpose_low_flow",
|
|
231
|
+
"PROBABILITY_KINDS",
|
|
232
|
+
"DEFAULT_AREA_RATIO_RANGE",
|
|
233
|
+
"LOW_FLOW_AREA_RATIO_RANGE",
|
|
234
|
+
# Low-flow frequency analysis
|
|
235
|
+
"LowFlowResults",
|
|
236
|
+
"LowFlowFrequency",
|
|
237
|
+
"annual_minimum_flow",
|
|
238
|
+
"LOW_FLOW_YEAR_TYPES",
|
|
239
|
+
# Flow regime metrics
|
|
240
|
+
"FlowRegime",
|
|
241
|
+
"richards_baker_flashiness",
|
|
242
|
+
"tqmean",
|
|
243
|
+
"baseflow_index",
|
|
244
|
+
"flow_duration_curve",
|
|
245
|
+
"DEFAULT_EXCEEDANCE_PCT",
|
|
246
|
+
"separate_baseflow",
|
|
247
|
+
"monthly_flow_summary",
|
|
248
|
+
"seasonal_flow_summary",
|
|
249
|
+
"BASEFLOW_METHODS",
|
|
250
|
+
"diel_variation",
|
|
251
|
+
"diel_variation_summary",
|
|
252
|
+
# USGS data retrieval
|
|
253
|
+
"USGSgage",
|
|
254
|
+
"USGSGage", # Alias for backwards compatibility
|
|
255
|
+
"GageAttributes",
|
|
256
|
+
"NoInstantaneousDataError",
|
|
257
|
+
"fetch_nwis_peaks",
|
|
258
|
+
"fetch_nwis_batch",
|
|
259
|
+
"save_flow_frame",
|
|
260
|
+
"load_flow_frame",
|
|
261
|
+
# Hydrograph
|
|
262
|
+
"Hydrograph",
|
|
263
|
+
# Bulletin 17C
|
|
264
|
+
"Bulletin17C",
|
|
265
|
+
"MethodOfMoments",
|
|
266
|
+
"ExpectedMomentsAlgorithm",
|
|
267
|
+
"FloodFrequencyAnalysis",
|
|
268
|
+
# Report
|
|
269
|
+
"HydroReport",
|
|
270
|
+
# Convenience
|
|
271
|
+
"analyze_gage",
|
|
272
|
+
]
|
flowfreq/_detrat.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Native port of peakfq's detrat: the Halloween determinant ratio, Wd.
|
|
2
|
+
|
|
3
|
+
TODO.md P3's second open item, independent of the ``var_mom``/``mse_ema``
|
|
4
|
+
port (``flowfreq._var_mom``/``flowfreq._mse_ema``). ``Wd`` is HWN's other
|
|
5
|
+
weighting input, alongside ``as_G_mse``: ``emafit.f``::
|
|
6
|
+
|
|
7
|
+
nG = n * Wd * as_G_mse / r_G_mse
|
|
8
|
+
|
|
9
|
+
``Wd`` corrects for something ``as_G_mse`` alone does not capture: on a
|
|
10
|
+
censored record, the at-site mean, variance, and skew estimates are
|
|
11
|
+
correlated with each other, so the skew estimate carries less *independent*
|
|
12
|
+
information than its own marginal MSE suggests. ``detrat`` measures that
|
|
13
|
+
via a determinant ratio -- ``det(I - F)`` for the full (mean, variance,
|
|
14
|
+
skew) system versus the (mean, variance)-only subsystem -- rather than
|
|
15
|
+
treating skew's uncertainty in isolation. Below an at-site skew magnitude of
|
|
16
|
+
0.04 the Fortran short-circuits to ``Wd = 1`` (``emafit.f:3654``); flowfreq
|
|
17
|
+
matches that, so this module is only ever exercised above that floor.
|
|
18
|
+
|
|
19
|
+
Everything here is a direct, function-for-function transcription of
|
|
20
|
+
``vendor/peakfqr/src/emafit.f``'s ``detrat`` and ``vendor/peakfqr/src/probfun.f``'s
|
|
21
|
+
``EXPMOMCDERIV`` (Greg Schwarz's derivation, cited in-line as "EQ n" the way
|
|
22
|
+
the Fortran comments do), checked against two Fortran oracles
|
|
23
|
+
``build_fortran/_emafort.pyf`` exposes (``expmomcderiv``, and Phase 1's
|
|
24
|
+
``detratsub``) -- see ``tests/fortran_parity/test_fortran_oracles.py``.
|
|
25
|
+
|
|
26
|
+
``EXPMOMCDERIV`` reuses ``flowfreq._var_mom._dexpect`` for the open-tail
|
|
27
|
+
expected moments and their Jacobian -- the same building block
|
|
28
|
+
``expmomderiv``/``d_est`` use -- rather than a second implementation of the
|
|
29
|
+
same truncated-gamma machinery.
|
|
30
|
+
|
|
31
|
+
Nothing in this module is wired into ``Bulletin17C``/``ExpectedMomentsAlgorithm``
|
|
32
|
+
yet.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import math
|
|
38
|
+
|
|
39
|
+
import numpy as np
|
|
40
|
+
|
|
41
|
+
from flowfreq._p3_moments import _fp_g3_cdf, m2p
|
|
42
|
+
from flowfreq._var_mom import _dexpect
|
|
43
|
+
|
|
44
|
+
__all__ = ["detrat"]
|
|
45
|
+
|
|
46
|
+
#: emafit.f:3654 -- below this at-site skew magnitude, Wd = 1 outright.
|
|
47
|
+
#: Matches bulletin17c.py's _HWN_SKEW_FLOOR.
|
|
48
|
+
_SKEW_FLOOR = 0.04
|
|
49
|
+
|
|
50
|
+
#: probfun.f:916 -- EXPMOMCDERIV's own "infinity", log10(1e20). A different
|
|
51
|
+
#: sentinel from flowfreq._var_mom's _D_EST_INF (1e19): this one is in the
|
|
52
|
+
#: same log10(flow) units as tl/tu themselves, not an arbitrarily large
|
|
53
|
+
#: number, and must match exactly for the open-tail Jacobian to line up
|
|
54
|
+
#: with what the Fortran computes.
|
|
55
|
+
_INF = 20.0
|
|
56
|
+
|
|
57
|
+
#: probfun.f:940 -- below this censoring probability, EXPMOMCDERIV falls
|
|
58
|
+
#: back to the unconditional (-inf, inf) moments/Jacobian rather than
|
|
59
|
+
#: dividing by a near-zero PICK.
|
|
60
|
+
_PICK_FLOOR = 1e-10
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _p2m(parms: np.ndarray) -> np.ndarray:
|
|
64
|
+
"""Pearson III params (tau, alpha, beta) -> central moments (mean, var, skew).
|
|
65
|
+
|
|
66
|
+
``probfun.f`` ``P2M`` (line 752): the direct gamma-distribution moment
|
|
67
|
+
formulas (variance ``alpha*beta**2``, skew ``2/sqrt(alpha)`` signed by
|
|
68
|
+
beta), not ``m2p``'s inverse via the noncentral-moment route -- though
|
|
69
|
+
the two agree exactly, since both describe the same distribution.
|
|
70
|
+
"""
|
|
71
|
+
tau, alpha, beta = float(parms[0]), float(parms[1]), float(parms[2])
|
|
72
|
+
mean = alpha * beta + tau
|
|
73
|
+
var = alpha * beta**2
|
|
74
|
+
skew = 2.0 / math.sqrt(alpha)
|
|
75
|
+
if beta < 0.0:
|
|
76
|
+
skew = -skew
|
|
77
|
+
return np.array([mean, var, skew])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _expmomcderiv(parms: np.ndarray, tl: float, tu: float):
|
|
81
|
+
"""E[X^j | X outside [tl, tu]] and the Jacobian of the resulting expected
|
|
82
|
+
|
|
83
|
+
central moments w.r.t. the fit's own central moments (mean, variance,
|
|
84
|
+
skew). ``probfun.f`` ``EXPMOMCDERIV`` (line 872), Schwarz's EQ 27.
|
|
85
|
+
|
|
86
|
+
"Outside [tl, tu]" -- the *censored* region for this threshold group,
|
|
87
|
+
not the perceived one -- is deliberate: this asks what the moments
|
|
88
|
+
would look like restricted to exactly the data ``detrat`` never gets to
|
|
89
|
+
see, which is the whole point of a censoring bias correction.
|
|
90
|
+
"""
|
|
91
|
+
mc = _p2m(parms)
|
|
92
|
+
mc1, mc2 = float(mc[0]), float(mc[1])
|
|
93
|
+
s = math.sqrt(mc2)
|
|
94
|
+
tau, alpha, beta = float(parms[0]), float(parms[1]), float(parms[2])
|
|
95
|
+
|
|
96
|
+
pil = _fp_g3_cdf(tl, parms)
|
|
97
|
+
pig = 1.0 - _fp_g3_cdf(tu, parms)
|
|
98
|
+
pick = pil + pig
|
|
99
|
+
|
|
100
|
+
if pick > _PICK_FLOOR:
|
|
101
|
+
mnel, jacl = _dexpect(tau, alpha, beta, -_INF, tl)
|
|
102
|
+
mneg, jacg = _dexpect(tau, alpha, beta, tu, _INF)
|
|
103
|
+
mne = (pil * mnel + pig * mneg) / pick
|
|
104
|
+
jac = (pil * jacl + pig * jacg) / pick
|
|
105
|
+
else:
|
|
106
|
+
mne, jac = _dexpect(tau, alpha, beta, -_INF, _INF)
|
|
107
|
+
|
|
108
|
+
db = np.array(
|
|
109
|
+
[
|
|
110
|
+
[0.0, 0.0, 0.0],
|
|
111
|
+
[2.0 * mc1, 0.0, 0.0],
|
|
112
|
+
[-3.0 * mc1**2 / s**3, 3.0 * mc1**3 / (2.0 * s**5), 0.0],
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
b = np.array(
|
|
116
|
+
[
|
|
117
|
+
[1.0, 0.0, 0.0],
|
|
118
|
+
[-2.0 * mc1, 1.0, 0.0],
|
|
119
|
+
[3.0 * mc1**2 / s**3, -3.0 * mc1 / s**3, 1.0 / s**3],
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
# d(tau, alpha, beta)/d(mean, variance, skew) directly -- not the same
|
|
123
|
+
# basis as flowfreq._var_mom._dpdm, which differentiates w.r.t.
|
|
124
|
+
# noncentral moments instead.
|
|
125
|
+
skew = float(mc[2])
|
|
126
|
+
tp = np.array(
|
|
127
|
+
[
|
|
128
|
+
[1.0, -1.0 / (s * skew), 2.0 * s / skew**2],
|
|
129
|
+
[0.0, 0.0, -8.0 / skew**3],
|
|
130
|
+
[0.0, skew / (4.0 * s), s / 2.0],
|
|
131
|
+
]
|
|
132
|
+
)
|
|
133
|
+
d3 = np.array(
|
|
134
|
+
[
|
|
135
|
+
[0.0, 0.0, 0.0],
|
|
136
|
+
[-2.0 * mne[0], 0.0, 0.0],
|
|
137
|
+
[
|
|
138
|
+
6.0 * mc1 / s**3 * mne[0] - 3.0 / s**3 * mne[1],
|
|
139
|
+
(-4.5 * mc1**2 * mne[0] + 4.5 * mc1 * mne[1] - 1.5 * mne[2]) / s**5,
|
|
140
|
+
0.0,
|
|
141
|
+
],
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
dedmc = db + b @ jac @ tp + d3
|
|
146
|
+
return mc, mne, dedmc
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def detrat(mc: np.ndarray, n: int, nobs: np.ndarray, tl: np.ndarray, tu: np.ndarray) -> float:
|
|
150
|
+
"""The Halloween determinant ratio, Wd. ``emafit.f`` ``detrat`` (line 3615).
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
mc : central moments (mean, variance, skew) of the at-site fit.
|
|
155
|
+
n : total record length (not the number of threshold groups).
|
|
156
|
+
nobs, tl, tu : perception-threshold groups, ``var_mom``'s convention --
|
|
157
|
+
``nobs[i]`` observations share the pair ``(tl[i], tu[i])``.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
float
|
|
162
|
+
``1.0`` below the 0.04 at-site skew floor; otherwise
|
|
163
|
+
``det(I - F) / det(I - F[:2, :2])``, the 3x3-vs-2x2 determinant
|
|
164
|
+
ratio.
|
|
165
|
+
"""
|
|
166
|
+
skew = float(mc[2])
|
|
167
|
+
if abs(skew) < _SKEW_FLOOR:
|
|
168
|
+
return 1.0
|
|
169
|
+
|
|
170
|
+
parms = m2p(mc)
|
|
171
|
+
mc1, mc2 = float(mc[0]), float(mc[1])
|
|
172
|
+
s = math.sqrt(mc2)
|
|
173
|
+
|
|
174
|
+
f0phi = np.zeros((3, 3))
|
|
175
|
+
for nobs_k, tl_k, tu_k in zip(nobs, tl, tu):
|
|
176
|
+
pick = 1.0 - (_fp_g3_cdf(tu_k, parms) - _fp_g3_cdf(tl_k, parms))
|
|
177
|
+
etak = float(nobs_k) / n
|
|
178
|
+
|
|
179
|
+
_, mne, dck = _expmomcderiv(parms, tl_k, tu_k)
|
|
180
|
+
|
|
181
|
+
# Expected central moments given censored, about the *population*
|
|
182
|
+
# mean/variance (not the conditional mean) -- Schwarz's EQ 23.
|
|
183
|
+
mcexpect = np.array(
|
|
184
|
+
[
|
|
185
|
+
mne[0],
|
|
186
|
+
mc1**2 - 2.0 * mc1 * mne[0] + mne[1],
|
|
187
|
+
(-(mc1**3) + 3.0 * mc1**2 * mne[0] - 3.0 * mc1 * mne[1] + mne[2]) / s**3,
|
|
188
|
+
]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
f1 = np.zeros((3, 3))
|
|
192
|
+
f1[1, 0] = 2.0 * pick * (mcexpect[0] - mc1)
|
|
193
|
+
f1[2, 0] = 3.0 * (pick * mcexpect[1] - mc2) / s**3
|
|
194
|
+
f1[2, 1] = 3.0 * (pick * mcexpect[2] - skew) / (2.0 * mc2)
|
|
195
|
+
|
|
196
|
+
f0phi += (f1 + pick * dck) * etak
|
|
197
|
+
|
|
198
|
+
imf = np.eye(3) - f0phi
|
|
199
|
+
imful = imf[:2, :2]
|
|
200
|
+
det2 = np.linalg.det(imful)
|
|
201
|
+
det3 = np.linalg.det(imf)
|
|
202
|
+
return det3 / det2
|