quantized-lab 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.
- quantized/__init__.py +11 -0
- quantized/__main__.py +14 -0
- quantized/api.py +220 -0
- quantized/app.py +206 -0
- quantized/calc/__init__.py +8 -0
- quantized/calc/_clipfit.py +76 -0
- quantized/calc/_natural_neighbor.py +219 -0
- quantized/calc/aggregate.py +159 -0
- quantized/calc/backgrounds.py +353 -0
- quantized/calc/baseline.py +349 -0
- quantized/calc/batch_fit.py +148 -0
- quantized/calc/constants.py +27 -0
- quantized/calc/corrections.py +192 -0
- quantized/calc/crystallography.py +400 -0
- quantized/calc/diffusion.py +120 -0
- quantized/calc/electrical.py +248 -0
- quantized/calc/electrochemistry.py +176 -0
- quantized/calc/element_data.json +1 -0
- quantized/calc/element_data.py +59 -0
- quantized/calc/errors.py +246 -0
- quantized/calc/figure.py +417 -0
- quantized/calc/figure_break.py +152 -0
- quantized/calc/figure_categorical.py +156 -0
- quantized/calc/figure_corner.py +229 -0
- quantized/calc/figure_facets.py +127 -0
- quantized/calc/figure_field.py +137 -0
- quantized/calc/figure_hitmap.py +116 -0
- quantized/calc/figure_labels.py +62 -0
- quantized/calc/figure_map.py +287 -0
- quantized/calc/figure_overrides.py +159 -0
- quantized/calc/figure_page.py +266 -0
- quantized/calc/figure_scale.py +125 -0
- quantized/calc/figure_statplots.py +167 -0
- quantized/calc/figure_styles.py +131 -0
- quantized/calc/figure_ternary.py +239 -0
- quantized/calc/figure_ticks.py +217 -0
- quantized/calc/fit_autoguess.py +156 -0
- quantized/calc/fit_bootstrap.py +163 -0
- quantized/calc/fit_bumps.py +258 -0
- quantized/calc/fit_constraints.py +114 -0
- quantized/calc/fit_equation.py +264 -0
- quantized/calc/fit_findxy.py +80 -0
- quantized/calc/fit_models.py +195 -0
- quantized/calc/fit_models_special.py +189 -0
- quantized/calc/fit_odr.py +99 -0
- quantized/calc/fit_scan.py +243 -0
- quantized/calc/fit_stats.py +215 -0
- quantized/calc/fitting.py +199 -0
- quantized/calc/formula.py +90 -0
- quantized/calc/global_curve_fit.py +305 -0
- quantized/calc/global_fit.py +181 -0
- quantized/calc/interp2d.py +260 -0
- quantized/calc/linecut.py +269 -0
- quantized/calc/magnetic.py +414 -0
- quantized/calc/magnetometry.py +464 -0
- quantized/calc/map.py +228 -0
- quantized/calc/mcmc.py +177 -0
- quantized/calc/optics.py +228 -0
- quantized/calc/pawley.py +251 -0
- quantized/calc/peak_batch.py +128 -0
- quantized/calc/peak_fit.py +259 -0
- quantized/calc/peak_integrate.py +104 -0
- quantized/calc/peak_multifit.py +260 -0
- quantized/calc/peak_track.py +134 -0
- quantized/calc/peaks.py +298 -0
- quantized/calc/peakshapes.py +85 -0
- quantized/calc/plotting.py +147 -0
- quantized/calc/processing.py +232 -0
- quantized/calc/qspace.py +48 -0
- quantized/calc/reductions.py +155 -0
- quantized/calc/reductions_fft.py +383 -0
- quantized/calc/refl_sld_presets.json +1 -0
- quantized/calc/reflectivity.py +80 -0
- quantized/calc/registry.py +303 -0
- quantized/calc/relaxation.py +119 -0
- quantized/calc/report.py +253 -0
- quantized/calc/report_emit.py +227 -0
- quantized/calc/resample.py +142 -0
- quantized/calc/rsm.py +91 -0
- quantized/calc/rsm_analyze.py +245 -0
- quantized/calc/semiconductor.py +488 -0
- quantized/calc/sld.py +131 -0
- quantized/calc/sld_formula.py +138 -0
- quantized/calc/spectral.py +357 -0
- quantized/calc/statplots.py +214 -0
- quantized/calc/stats.py +399 -0
- quantized/calc/stats_anova2.py +202 -0
- quantized/calc/stats_anova_ext.py +338 -0
- quantized/calc/stats_dist.py +196 -0
- quantized/calc/stats_glm.py +245 -0
- quantized/calc/stats_multivar.py +289 -0
- quantized/calc/stats_roc.py +157 -0
- quantized/calc/stats_survival.py +261 -0
- quantized/calc/stats_tests.py +380 -0
- quantized/calc/substrates.py +181 -0
- quantized/calc/superconductor.py +359 -0
- quantized/calc/surface_fit.py +290 -0
- quantized/calc/surface_models.py +156 -0
- quantized/calc/thermal.py +119 -0
- quantized/calc/thin_film.py +425 -0
- quantized/calc/unit_convert.py +259 -0
- quantized/calc/units.py +80 -0
- quantized/calc/vacuum.py +290 -0
- quantized/calc/xray.py +169 -0
- quantized/cli.py +214 -0
- quantized/datastruct.py +153 -0
- quantized/io/__init__.py +11 -0
- quantized/io/_hdf5_layout.py +308 -0
- quantized/io/_jcamp_asdf.py +135 -0
- quantized/io/_xrdml_scan.py +291 -0
- quantized/io/base.py +82 -0
- quantized/io/bruker_brml.py +177 -0
- quantized/io/bruker_raw.py +158 -0
- quantized/io/cif.py +266 -0
- quantized/io/consolidated.py +122 -0
- quantized/io/delimited.py +222 -0
- quantized/io/excel.py +135 -0
- quantized/io/hdf5.py +192 -0
- quantized/io/import_filters.py +178 -0
- quantized/io/import_preview.py +262 -0
- quantized/io/jcamp.py +179 -0
- quantized/io/lakeshore.py +163 -0
- quantized/io/ncnr.py +278 -0
- quantized/io/netcdf.py +195 -0
- quantized/io/opus.py +231 -0
- quantized/io/origin.py +346 -0
- quantized/io/origin_com.py +194 -0
- quantized/io/origin_project/__init__.py +221 -0
- quantized/io/origin_project/annotation_marks.py +288 -0
- quantized/io/origin_project/container.py +262 -0
- quantized/io/origin_project/curve_style_color.py +359 -0
- quantized/io/origin_project/figure_geometry.py +108 -0
- quantized/io/origin_project/figure_layers.py +333 -0
- quantized/io/origin_project/figure_text.py +258 -0
- quantized/io/origin_project/figures.py +210 -0
- quantized/io/origin_project/figures_opju.py +440 -0
- quantized/io/origin_project/notes.py +302 -0
- quantized/io/origin_project/opj.py +459 -0
- quantized/io/origin_project/opj_curves.py +297 -0
- quantized/io/origin_project/opj_shapes.py +148 -0
- quantized/io/origin_project/opju.py +146 -0
- quantized/io/origin_project/opju_axis_real_form.py +418 -0
- quantized/io/origin_project/opju_axis_specimen_form.py +167 -0
- quantized/io/origin_project/opju_codec.py +370 -0
- quantized/io/origin_project/opju_curves.py +497 -0
- quantized/io/origin_project/opju_curves_allcols.py +258 -0
- quantized/io/origin_project/opju_figure_curves.py +302 -0
- quantized/io/origin_project/opju_figure_text.py +245 -0
- quantized/io/origin_project/opju_reports.py +129 -0
- quantized/io/origin_project/origin_richtext.py +145 -0
- quantized/io/origin_project/preview.py +132 -0
- quantized/io/origin_project/templates.py +314 -0
- quantized/io/origin_project/tree.py +379 -0
- quantized/io/origin_project/tree_opju.py +228 -0
- quantized/io/origin_project/windows.py +238 -0
- quantized/io/origin_project/windows_opju.py +393 -0
- quantized/io/origin_project/writer.py +156 -0
- quantized/io/origin_project/writer_blocks.py +282 -0
- quantized/io/qd.py +380 -0
- quantized/io/refl1d.py +132 -0
- quantized/io/registry.py +210 -0
- quantized/io/report_export.py +347 -0
- quantized/io/rigaku.py +100 -0
- quantized/io/sims.py +398 -0
- quantized/io/spc.py +311 -0
- quantized/io/xrd_csv.py +308 -0
- quantized/io/xrdml.py +394 -0
- quantized/jobs.py +173 -0
- quantized/plugins/__init__.py +50 -0
- quantized/plugins/contract.py +111 -0
- quantized/plugins/loader.py +394 -0
- quantized/plugins/steps.py +90 -0
- quantized/routes/__init__.py +7 -0
- quantized/routes/_bookcache.py +62 -0
- quantized/routes/_export_common.py +27 -0
- quantized/routes/_payload.py +58 -0
- quantized/routes/_uploadcache.py +59 -0
- quantized/routes/aggregate.py +46 -0
- quantized/routes/baseline.py +210 -0
- quantized/routes/books.py +117 -0
- quantized/routes/calc.py +56 -0
- quantized/routes/corrections.py +78 -0
- quantized/routes/crystallography.py +80 -0
- quantized/routes/diffusion.py +58 -0
- quantized/routes/electrical.py +101 -0
- quantized/routes/electrochemistry.py +83 -0
- quantized/routes/export.py +280 -0
- quantized/routes/export_facets.py +83 -0
- quantized/routes/export_figures.py +471 -0
- quantized/routes/export_page.py +125 -0
- quantized/routes/fitting.py +379 -0
- quantized/routes/fitting_bumps.py +97 -0
- quantized/routes/import_template.py +97 -0
- quantized/routes/import_wizard.py +150 -0
- quantized/routes/jobs_api.py +59 -0
- quantized/routes/magnetic.py +135 -0
- quantized/routes/magnetometry.py +133 -0
- quantized/routes/optics.py +98 -0
- quantized/routes/parsers.py +281 -0
- quantized/routes/peaks.py +184 -0
- quantized/routes/plot.py +103 -0
- quantized/routes/reductions.py +121 -0
- quantized/routes/reference.py +58 -0
- quantized/routes/reflectivity.py +91 -0
- quantized/routes/report_export.py +119 -0
- quantized/routes/rsm.py +136 -0
- quantized/routes/samples.py +32 -0
- quantized/routes/semiconductor.py +207 -0
- quantized/routes/sld.py +42 -0
- quantized/routes/spectral.py +54 -0
- quantized/routes/statplots.py +99 -0
- quantized/routes/stats.py +418 -0
- quantized/routes/stats_design.py +321 -0
- quantized/routes/substrates.py +46 -0
- quantized/routes/superconductor.py +139 -0
- quantized/routes/thermal.py +57 -0
- quantized/routes/thin_film.py +153 -0
- quantized/routes/vacuum.py +113 -0
- quantized/routes/xray.py +32 -0
- quantized/samples/demo_vsm.csv +42 -0
- quantized/server_launch.py +251 -0
- quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
- quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
- quantized/web/assets/index-BHmmCL-x.js +27 -0
- quantized/web/assets/index-BiZzN7J6.css +1 -0
- quantized/web/index.html +13 -0
- quantized/web/loading.html +69 -0
- quantized_lab-0.8.0.dist-info/METADATA +122 -0
- quantized_lab-0.8.0.dist-info/RECORD +233 -0
- quantized_lab-0.8.0.dist-info/WHEEL +4 -0
- quantized_lab-0.8.0.dist-info/entry_points.txt +4 -0
- quantized_lab-0.8.0.dist-info/licenses/LICENSE +201 -0
- quantized_lab-0.8.0.dist-info/licenses/NOTICE +11 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Thin routes: designed-experiment ANOVA + post-hoc + the test chooser + GLM/survival/ROC.
|
|
2
|
+
|
|
3
|
+
Split from routes/stats.py (500-line module ceiling); same /api/stats prefix.
|
|
4
|
+
GAP_PLAN #30 adds GLM (logistic/Poisson), survival (KM/log-rank/Cox), and ROC methods.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from fastapi import APIRouter, HTTPException
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from quantized.calc.stats_anova2 import adjust_pvalues, anova2, dunnett_test, tukey_hsd
|
|
16
|
+
from quantized.calc.stats_anova_ext import anova2_unbalanced, repeated_measures_anova
|
|
17
|
+
from quantized.calc.stats_glm import logistic_regression, poisson_regression
|
|
18
|
+
from quantized.calc.stats_roc import auc, roc_curve, youden_optimal_threshold
|
|
19
|
+
from quantized.calc.stats_survival import cox_proportional_hazards, kaplan_meier, logrank_test
|
|
20
|
+
from quantized.calc.stats_tests import recommend_test
|
|
21
|
+
from quantized.routes._payload import to_jsonable
|
|
22
|
+
|
|
23
|
+
router = APIRouter(prefix="/api/stats", tags=["stats"])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _wrap(result: dict[str, Any]) -> dict[str, Any]:
|
|
27
|
+
return to_jsonable(result) # type: ignore[no-any-return]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Anova2Request(BaseModel):
|
|
31
|
+
cells: list[list[list[float]]] # [A-level][B-level][replicates], balanced
|
|
32
|
+
alpha: float = 0.05
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.post("/anova2")
|
|
36
|
+
def anova2_route(req: Anova2Request) -> dict[str, Any]:
|
|
37
|
+
"""Balanced two-way factorial ANOVA with interaction."""
|
|
38
|
+
try:
|
|
39
|
+
return _wrap(anova2(req.cells, alpha=req.alpha))
|
|
40
|
+
except (ValueError, IndexError) as exc:
|
|
41
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Anova2UnbalancedRequest(BaseModel):
|
|
45
|
+
values: list[float]
|
|
46
|
+
factor_a: list[str]
|
|
47
|
+
factor_b: list[str]
|
|
48
|
+
ss_type: int = 3 # 2 or 3
|
|
49
|
+
alpha: float = 0.05
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.post("/anova2-unbalanced")
|
|
53
|
+
def anova2_unbalanced_route(req: Anova2UnbalancedRequest) -> dict[str, Any]:
|
|
54
|
+
"""Unbalanced two-way ANOVA (Type II/III SS) from long-format columns."""
|
|
55
|
+
try:
|
|
56
|
+
return _wrap(
|
|
57
|
+
anova2_unbalanced(
|
|
58
|
+
np.asarray(req.values, dtype=float),
|
|
59
|
+
req.factor_a, req.factor_b,
|
|
60
|
+
ss_type=req.ss_type, alpha=req.alpha,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
except (ValueError, IndexError) as exc:
|
|
64
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RepeatedMeasuresRequest(BaseModel):
|
|
68
|
+
data: list[list[float]] # rows = subjects, columns = conditions
|
|
69
|
+
alpha: float = 0.05
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.post("/anova-rm")
|
|
73
|
+
def anova_rm_route(req: RepeatedMeasuresRequest) -> dict[str, Any]:
|
|
74
|
+
"""One-way repeated-measures (within-subjects) ANOVA + sphericity."""
|
|
75
|
+
try:
|
|
76
|
+
return _wrap(repeated_measures_anova(req.data, alpha=req.alpha))
|
|
77
|
+
except (ValueError, IndexError) as exc:
|
|
78
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class PostHocRequest(BaseModel):
|
|
82
|
+
groups: list[list[float]]
|
|
83
|
+
alpha: float = 0.05
|
|
84
|
+
control: int = 0 # dunnett only
|
|
85
|
+
alternative: str = "two-sided" # dunnett only
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@router.post("/tukey")
|
|
89
|
+
def tukey_route(req: PostHocRequest) -> dict[str, Any]:
|
|
90
|
+
"""Tukey HSD all-pairs post-hoc."""
|
|
91
|
+
try:
|
|
92
|
+
return _wrap(tukey_hsd([np.asarray(g, dtype=float) for g in req.groups], alpha=req.alpha))
|
|
93
|
+
except (ValueError, IndexError) as exc:
|
|
94
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@router.post("/dunnett")
|
|
98
|
+
def dunnett_route(req: PostHocRequest) -> dict[str, Any]:
|
|
99
|
+
"""Dunnett many-to-one post-hoc vs a control group."""
|
|
100
|
+
try:
|
|
101
|
+
return _wrap(
|
|
102
|
+
dunnett_test(
|
|
103
|
+
[np.asarray(g, dtype=float) for g in req.groups],
|
|
104
|
+
control=req.control, alpha=req.alpha, alternative=req.alternative,
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
except (ValueError, IndexError) as exc:
|
|
108
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class RecommendRequest(BaseModel):
|
|
112
|
+
groups: list[list[float]]
|
|
113
|
+
paired: bool = False
|
|
114
|
+
alpha: float = 0.05
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@router.post("/recommend")
|
|
118
|
+
def recommend_route(req: RecommendRequest) -> dict[str, Any]:
|
|
119
|
+
"""The 'which test?' chooser: assumption checks -> recommended test."""
|
|
120
|
+
try:
|
|
121
|
+
return _wrap(
|
|
122
|
+
recommend_test(
|
|
123
|
+
[np.asarray(g, dtype=float) for g in req.groups],
|
|
124
|
+
paired=req.paired, alpha=req.alpha,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
except (ValueError, IndexError) as exc:
|
|
128
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class AdjustPRequest(BaseModel):
|
|
132
|
+
p_values: list[float]
|
|
133
|
+
method: str = "holm"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@router.post("/adjust-p")
|
|
137
|
+
def adjust_p_route(req: AdjustPRequest) -> dict[str, Any]:
|
|
138
|
+
"""Bonferroni / Holm / Benjamini-Hochberg p-value adjustment."""
|
|
139
|
+
try:
|
|
140
|
+
return _wrap(adjust_pvalues(req.p_values, method=req.method))
|
|
141
|
+
except (ValueError, IndexError) as exc:
|
|
142
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ── GLM (Logistic & Poisson) ──────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class GlmRequest(BaseModel):
|
|
149
|
+
"""Request for logistic or Poisson GLM.
|
|
150
|
+
|
|
151
|
+
``predictors`` is a list of *k predictor columns* (each a same-length
|
|
152
|
+
array of n observations) — ``[[x1_1..x1_n], [x2_1..x2_n], ...]`` — not a
|
|
153
|
+
list of n row records. Same convention as ``calc.stats_glm``/
|
|
154
|
+
``calc.stats_multivar``; the intercept is added automatically.
|
|
155
|
+
"""
|
|
156
|
+
predictors: list[list[float]]
|
|
157
|
+
y: list[float]
|
|
158
|
+
alpha: float = 0.05
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@router.post("/glm-logistic")
|
|
162
|
+
def glm_logistic_route(req: GlmRequest) -> dict[str, Any]:
|
|
163
|
+
"""Logistic regression with coefficients, SEs, z-stats, p-values, AIC."""
|
|
164
|
+
try:
|
|
165
|
+
predictors = [np.asarray(c, dtype=float) for c in req.predictors]
|
|
166
|
+
y = np.asarray(req.y, dtype=float)
|
|
167
|
+
return _wrap(logistic_regression(predictors, y, alpha=req.alpha))
|
|
168
|
+
except RuntimeError as exc:
|
|
169
|
+
raise HTTPException(status_code=501, detail=str(exc)) from exc
|
|
170
|
+
except (ValueError, IndexError) as exc:
|
|
171
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@router.post("/glm-poisson")
|
|
175
|
+
def glm_poisson_route(req: GlmRequest) -> dict[str, Any]:
|
|
176
|
+
"""Poisson regression with coefficients, SEs, z-stats, p-values, AIC."""
|
|
177
|
+
try:
|
|
178
|
+
predictors = [np.asarray(c, dtype=float) for c in req.predictors]
|
|
179
|
+
y = np.asarray(req.y, dtype=float)
|
|
180
|
+
return _wrap(poisson_regression(predictors, y, alpha=req.alpha))
|
|
181
|
+
except RuntimeError as exc:
|
|
182
|
+
raise HTTPException(status_code=501, detail=str(exc)) from exc
|
|
183
|
+
except (ValueError, IndexError) as exc:
|
|
184
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ── Survival Analysis ─────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class SurvivalRequest(BaseModel):
|
|
191
|
+
"""Request for Kaplan-Meier or log-rank test."""
|
|
192
|
+
time: list[float]
|
|
193
|
+
event: list[float]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class KaplanMeierRequest(SurvivalRequest):
|
|
197
|
+
"""Kaplan-Meier curve request."""
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class LogRankRequest(BaseModel):
|
|
202
|
+
"""Request for log-rank test between two groups."""
|
|
203
|
+
time1: list[float]
|
|
204
|
+
event1: list[float]
|
|
205
|
+
time2: list[float]
|
|
206
|
+
event2: list[float]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class CoxRequest(BaseModel):
|
|
210
|
+
"""Request for Cox proportional-hazards model.
|
|
211
|
+
|
|
212
|
+
``predictors`` is a list of *k predictor columns* (each length n),
|
|
213
|
+
same convention as ``GlmRequest.predictors`` — not a list of row
|
|
214
|
+
records.
|
|
215
|
+
"""
|
|
216
|
+
time: list[float]
|
|
217
|
+
event: list[float]
|
|
218
|
+
predictors: list[list[float]]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@router.post("/kaplan-meier")
|
|
222
|
+
def kaplan_meier_route(req: KaplanMeierRequest) -> dict[str, Any]:
|
|
223
|
+
"""Kaplan-Meier survival curve with Greenwood CIs."""
|
|
224
|
+
try:
|
|
225
|
+
time = np.asarray(req.time, dtype=float)
|
|
226
|
+
event = np.asarray(req.event, dtype=float)
|
|
227
|
+
return _wrap(kaplan_meier(time, event))
|
|
228
|
+
except RuntimeError as exc:
|
|
229
|
+
raise HTTPException(status_code=501, detail=str(exc)) from exc
|
|
230
|
+
except (ValueError, IndexError) as exc:
|
|
231
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@router.post("/logrank")
|
|
235
|
+
def logrank_route(req: LogRankRequest) -> dict[str, Any]:
|
|
236
|
+
"""Log-rank test comparing two survival curves."""
|
|
237
|
+
try:
|
|
238
|
+
return _wrap(
|
|
239
|
+
logrank_test(
|
|
240
|
+
np.asarray(req.time1, dtype=float), np.asarray(req.event1, dtype=float),
|
|
241
|
+
np.asarray(req.time2, dtype=float), np.asarray(req.event2, dtype=float),
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
except RuntimeError as exc:
|
|
245
|
+
raise HTTPException(status_code=501, detail=str(exc)) from exc
|
|
246
|
+
except (ValueError, IndexError) as exc:
|
|
247
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@router.post("/cox-ph")
|
|
251
|
+
def cox_ph_route(req: CoxRequest) -> dict[str, Any]:
|
|
252
|
+
"""Cox proportional-hazards model."""
|
|
253
|
+
try:
|
|
254
|
+
predictors = [np.asarray(c, dtype=float) for c in req.predictors]
|
|
255
|
+
return _wrap(
|
|
256
|
+
cox_proportional_hazards(
|
|
257
|
+
np.asarray(req.time, dtype=float), np.asarray(req.event, dtype=float),
|
|
258
|
+
predictors,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
except RuntimeError as exc:
|
|
262
|
+
raise HTTPException(status_code=501, detail=str(exc)) from exc
|
|
263
|
+
except (ValueError, IndexError) as exc:
|
|
264
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ── ROC & AUC ─────────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class RocRequest(BaseModel):
|
|
271
|
+
"""Request for ROC curve."""
|
|
272
|
+
y_true: list[float]
|
|
273
|
+
y_score: list[float]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class AucRequest(BaseModel):
|
|
277
|
+
"""Request for AUC computation."""
|
|
278
|
+
fpr: list[float]
|
|
279
|
+
tpr: list[float]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class YoudenRequest(BaseModel):
|
|
283
|
+
"""Request for Youden optimal threshold."""
|
|
284
|
+
fpr: list[float]
|
|
285
|
+
tpr: list[float]
|
|
286
|
+
thresholds: list[float]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@router.post("/roc-curve")
|
|
290
|
+
def roc_curve_route(req: RocRequest) -> dict[str, Any]:
|
|
291
|
+
"""ROC curve points (FPR, TPR) at all thresholds."""
|
|
292
|
+
try:
|
|
293
|
+
y_true = np.asarray(req.y_true, dtype=float)
|
|
294
|
+
y_score = np.asarray(req.y_score, dtype=float)
|
|
295
|
+
return _wrap(roc_curve(y_true, y_score))
|
|
296
|
+
except (ValueError, IndexError) as exc:
|
|
297
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@router.post("/auc")
|
|
301
|
+
def auc_route(req: AucRequest) -> dict[str, Any]:
|
|
302
|
+
"""Area Under the ROC Curve (trapezoidal rule)."""
|
|
303
|
+
try:
|
|
304
|
+
auc_val = auc(np.asarray(req.fpr, dtype=float), np.asarray(req.tpr, dtype=float))
|
|
305
|
+
return _wrap({"auc": auc_val})
|
|
306
|
+
except (ValueError, IndexError) as exc:
|
|
307
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@router.post("/youden-threshold")
|
|
311
|
+
def youden_route(req: YoudenRequest) -> dict[str, Any]:
|
|
312
|
+
"""Youden J-statistic optimal threshold selection."""
|
|
313
|
+
try:
|
|
314
|
+
return _wrap(
|
|
315
|
+
youden_optimal_threshold(
|
|
316
|
+
np.asarray(req.fpr, dtype=float), np.asarray(req.tpr, dtype=float),
|
|
317
|
+
np.asarray(req.thresholds, dtype=float),
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
except (ValueError, IndexError) as exc:
|
|
321
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Thin substrate routes. Wraps ``calc.substrates`` (reference table +
|
|
2
|
+
lattice-mismatch formula). Validate -> call the pure fn -> serialize.
|
|
3
|
+
|
|
4
|
+
GET endpoints expose the substrate reference table (list + single lookup);
|
|
5
|
+
the POST endpoint computes the epitaxial lattice mismatch f = (a_f - a_s)/a_s.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter, HTTPException
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from quantized.calc import substrates
|
|
16
|
+
|
|
17
|
+
router = APIRouter(prefix="/api/substrates", tags=["substrates"])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MismatchRequest(BaseModel):
|
|
21
|
+
a_film: float
|
|
22
|
+
a_sub: float
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get("")
|
|
26
|
+
def list_substrates() -> dict[str, list[dict[str, Any]]]:
|
|
27
|
+
"""Full substrate reference table (list of property dicts)."""
|
|
28
|
+
return {"substrates": substrates.substrate_table()}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.get("/{name}")
|
|
32
|
+
def get_substrate(name: str) -> dict[str, Any]:
|
|
33
|
+
"""Single substrate property card by name (case-insensitive)."""
|
|
34
|
+
try:
|
|
35
|
+
return substrates.get_substrate(name)
|
|
36
|
+
except ValueError as exc:
|
|
37
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.post("/mismatch")
|
|
41
|
+
def mismatch(req: MismatchRequest) -> dict[str, Any]:
|
|
42
|
+
"""f = (a_film - a_sub)/a_sub, with tensile/compressive/matched label."""
|
|
43
|
+
try:
|
|
44
|
+
return substrates.lattice_mismatch(req.a_film, req.a_sub)
|
|
45
|
+
except ValueError as exc:
|
|
46
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Thin superconductivity routes. Wraps ``calc.superconductor`` (pure formulas):
|
|
2
|
+
material presets / London depth / coherence length / GL parameter / critical
|
|
3
|
+
fields / depairing current / BCS gap. Validate -> call the pure fn -> serialize.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, HTTPException
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from quantized.calc import superconductor
|
|
15
|
+
|
|
16
|
+
router = APIRouter(prefix="/api/superconductor", tags=["superconductor"])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LondonRequest(BaseModel):
|
|
20
|
+
lambda0: float | None = None
|
|
21
|
+
t: float
|
|
22
|
+
tc: float | None = None
|
|
23
|
+
material: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CoherenceRequest(BaseModel):
|
|
27
|
+
xi0: float | None = None
|
|
28
|
+
t: float
|
|
29
|
+
tc: float | None = None
|
|
30
|
+
material: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GlRequest(BaseModel):
|
|
34
|
+
lambda_: float | None = None
|
|
35
|
+
xi: float | None = None
|
|
36
|
+
material: str | None = None
|
|
37
|
+
t: float | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CriticalFieldsRequest(BaseModel):
|
|
41
|
+
hc0: float | None = None
|
|
42
|
+
tc: float | None = None
|
|
43
|
+
t: float
|
|
44
|
+
material: str | None = None
|
|
45
|
+
lambda_: float | None = None
|
|
46
|
+
xi: float | None = None
|
|
47
|
+
kappa: float | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DepairingRequest(BaseModel):
|
|
51
|
+
hc0: float | None = None
|
|
52
|
+
lambda0: float | None = None
|
|
53
|
+
tc: float | None = None
|
|
54
|
+
t: float
|
|
55
|
+
material: str | None = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BcsGapRequest(BaseModel):
|
|
59
|
+
tc: float
|
|
60
|
+
t: float | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PresetsRequest(BaseModel):
|
|
64
|
+
material: str | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _call(fn: Callable[..., dict[str, Any]], *args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
68
|
+
try:
|
|
69
|
+
return fn(*args, **kwargs)
|
|
70
|
+
except ValueError as exc:
|
|
71
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@router.post("/material-presets")
|
|
75
|
+
def material_presets(req: PresetsRequest) -> dict[str, Any]:
|
|
76
|
+
"""Material property table (all, or a single named material)."""
|
|
77
|
+
return _call(superconductor.material_presets, req.material)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@router.post("/london-depth")
|
|
81
|
+
def london_depth(req: LondonRequest) -> dict[str, Any]:
|
|
82
|
+
"""λ(T) = λ₀/√(1 − (T/Tc)⁴) (nm)."""
|
|
83
|
+
return _call(
|
|
84
|
+
superconductor.london_depth, req.lambda0, req.t, req.tc, material=req.material
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@router.post("/coherence-length")
|
|
89
|
+
def coherence_length(req: CoherenceRequest) -> dict[str, Any]:
|
|
90
|
+
"""ξ(T) = ξ₀/√(1 − (T/Tc)²) (nm)."""
|
|
91
|
+
return _call(
|
|
92
|
+
superconductor.coherence_length, req.xi0, req.t, req.tc, material=req.material
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@router.post("/gl-parameter")
|
|
97
|
+
def gl_parameter(req: GlRequest) -> dict[str, Any]:
|
|
98
|
+
"""κ = λ/ξ; type I/II classification."""
|
|
99
|
+
return _call(
|
|
100
|
+
superconductor.gl_parameter,
|
|
101
|
+
req.lambda_,
|
|
102
|
+
req.xi,
|
|
103
|
+
material=req.material,
|
|
104
|
+
t=req.t,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@router.post("/critical-fields")
|
|
109
|
+
def critical_fields(req: CriticalFieldsRequest) -> dict[str, Any]:
|
|
110
|
+
"""Hc, Hc1, Hc2 (Oe) and superconductor type."""
|
|
111
|
+
return _call(
|
|
112
|
+
superconductor.critical_fields,
|
|
113
|
+
req.hc0,
|
|
114
|
+
req.tc,
|
|
115
|
+
req.t,
|
|
116
|
+
material=req.material,
|
|
117
|
+
lambda_=req.lambda_,
|
|
118
|
+
xi=req.xi,
|
|
119
|
+
kappa=req.kappa,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@router.post("/depairing-current")
|
|
124
|
+
def depairing_current(req: DepairingRequest) -> dict[str, Any]:
|
|
125
|
+
"""Jd = Hc(T)/(3√6·π·λ(T)) (A/cm² and MA/cm²)."""
|
|
126
|
+
return _call(
|
|
127
|
+
superconductor.depairing_current,
|
|
128
|
+
req.hc0,
|
|
129
|
+
req.lambda0,
|
|
130
|
+
req.tc,
|
|
131
|
+
req.t,
|
|
132
|
+
material=req.material,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@router.post("/bcs-gap")
|
|
137
|
+
def bcs_gap(req: BcsGapRequest) -> dict[str, Any]:
|
|
138
|
+
"""Δ₀ = 1.764·k_B·Tc (meV); Mühlschlegel Δ(T) when T given."""
|
|
139
|
+
return _call(superconductor.bcs_gap, req.tc, req.t)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Thin thermal-property routes. Wraps ``calc.thermal`` (pure formulas):
|
|
2
|
+
Wiedemann-Franz law / Debye temperature / thermal diffusivity. Validate ->
|
|
3
|
+
call the pure fn -> serialize.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, HTTPException
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from quantized.calc import thermal
|
|
15
|
+
|
|
16
|
+
router = APIRouter(prefix="/api/thermal", tags=["thermal"])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WiedemannFranzRequest(BaseModel):
|
|
20
|
+
sigma: float # electrical conductivity (S/cm)
|
|
21
|
+
temperature: float # K
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DebyeRequest(BaseModel):
|
|
25
|
+
v_s: float # average sound velocity (m/s)
|
|
26
|
+
n: float # atomic number density (atoms/m^3)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DiffusivityRequest(BaseModel):
|
|
30
|
+
kappa: float # thermal conductivity (W/m/K)
|
|
31
|
+
rho: float # mass density (kg/m^3)
|
|
32
|
+
cp: float # specific heat (J/kg/K)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _call(fn: Callable[..., dict[str, Any]], *args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
36
|
+
try:
|
|
37
|
+
return fn(*args, **kwargs)
|
|
38
|
+
except ValueError as exc:
|
|
39
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.post("/wiedemann-franz")
|
|
43
|
+
def wiedemann_franz(req: WiedemannFranzRequest) -> dict[str, Any]:
|
|
44
|
+
"""κ = L₀·σ·T (W/(m·K))."""
|
|
45
|
+
return _call(thermal.wiedemann_franz, req.sigma, req.temperature)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@router.post("/debye")
|
|
49
|
+
def debye(req: DebyeRequest) -> dict[str, Any]:
|
|
50
|
+
"""Θ_D = (ħ/k_B)·v_s·(6π²·n)^(1/3) (K)."""
|
|
51
|
+
return _call(thermal.debye_temperature, req.v_s, req.n)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.post("/diffusivity")
|
|
55
|
+
def diffusivity(req: DiffusivityRequest) -> dict[str, Any]:
|
|
56
|
+
"""α = κ/(ρ·c_p) (m²/s)."""
|
|
57
|
+
return _call(thermal.thermal_diffusivity, req.kappa, req.rho, req.cp)
|