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.
Files changed (233) hide show
  1. quantized/__init__.py +11 -0
  2. quantized/__main__.py +14 -0
  3. quantized/api.py +220 -0
  4. quantized/app.py +206 -0
  5. quantized/calc/__init__.py +8 -0
  6. quantized/calc/_clipfit.py +76 -0
  7. quantized/calc/_natural_neighbor.py +219 -0
  8. quantized/calc/aggregate.py +159 -0
  9. quantized/calc/backgrounds.py +353 -0
  10. quantized/calc/baseline.py +349 -0
  11. quantized/calc/batch_fit.py +148 -0
  12. quantized/calc/constants.py +27 -0
  13. quantized/calc/corrections.py +192 -0
  14. quantized/calc/crystallography.py +400 -0
  15. quantized/calc/diffusion.py +120 -0
  16. quantized/calc/electrical.py +248 -0
  17. quantized/calc/electrochemistry.py +176 -0
  18. quantized/calc/element_data.json +1 -0
  19. quantized/calc/element_data.py +59 -0
  20. quantized/calc/errors.py +246 -0
  21. quantized/calc/figure.py +417 -0
  22. quantized/calc/figure_break.py +152 -0
  23. quantized/calc/figure_categorical.py +156 -0
  24. quantized/calc/figure_corner.py +229 -0
  25. quantized/calc/figure_facets.py +127 -0
  26. quantized/calc/figure_field.py +137 -0
  27. quantized/calc/figure_hitmap.py +116 -0
  28. quantized/calc/figure_labels.py +62 -0
  29. quantized/calc/figure_map.py +287 -0
  30. quantized/calc/figure_overrides.py +159 -0
  31. quantized/calc/figure_page.py +266 -0
  32. quantized/calc/figure_scale.py +125 -0
  33. quantized/calc/figure_statplots.py +167 -0
  34. quantized/calc/figure_styles.py +131 -0
  35. quantized/calc/figure_ternary.py +239 -0
  36. quantized/calc/figure_ticks.py +217 -0
  37. quantized/calc/fit_autoguess.py +156 -0
  38. quantized/calc/fit_bootstrap.py +163 -0
  39. quantized/calc/fit_bumps.py +258 -0
  40. quantized/calc/fit_constraints.py +114 -0
  41. quantized/calc/fit_equation.py +264 -0
  42. quantized/calc/fit_findxy.py +80 -0
  43. quantized/calc/fit_models.py +195 -0
  44. quantized/calc/fit_models_special.py +189 -0
  45. quantized/calc/fit_odr.py +99 -0
  46. quantized/calc/fit_scan.py +243 -0
  47. quantized/calc/fit_stats.py +215 -0
  48. quantized/calc/fitting.py +199 -0
  49. quantized/calc/formula.py +90 -0
  50. quantized/calc/global_curve_fit.py +305 -0
  51. quantized/calc/global_fit.py +181 -0
  52. quantized/calc/interp2d.py +260 -0
  53. quantized/calc/linecut.py +269 -0
  54. quantized/calc/magnetic.py +414 -0
  55. quantized/calc/magnetometry.py +464 -0
  56. quantized/calc/map.py +228 -0
  57. quantized/calc/mcmc.py +177 -0
  58. quantized/calc/optics.py +228 -0
  59. quantized/calc/pawley.py +251 -0
  60. quantized/calc/peak_batch.py +128 -0
  61. quantized/calc/peak_fit.py +259 -0
  62. quantized/calc/peak_integrate.py +104 -0
  63. quantized/calc/peak_multifit.py +260 -0
  64. quantized/calc/peak_track.py +134 -0
  65. quantized/calc/peaks.py +298 -0
  66. quantized/calc/peakshapes.py +85 -0
  67. quantized/calc/plotting.py +147 -0
  68. quantized/calc/processing.py +232 -0
  69. quantized/calc/qspace.py +48 -0
  70. quantized/calc/reductions.py +155 -0
  71. quantized/calc/reductions_fft.py +383 -0
  72. quantized/calc/refl_sld_presets.json +1 -0
  73. quantized/calc/reflectivity.py +80 -0
  74. quantized/calc/registry.py +303 -0
  75. quantized/calc/relaxation.py +119 -0
  76. quantized/calc/report.py +253 -0
  77. quantized/calc/report_emit.py +227 -0
  78. quantized/calc/resample.py +142 -0
  79. quantized/calc/rsm.py +91 -0
  80. quantized/calc/rsm_analyze.py +245 -0
  81. quantized/calc/semiconductor.py +488 -0
  82. quantized/calc/sld.py +131 -0
  83. quantized/calc/sld_formula.py +138 -0
  84. quantized/calc/spectral.py +357 -0
  85. quantized/calc/statplots.py +214 -0
  86. quantized/calc/stats.py +399 -0
  87. quantized/calc/stats_anova2.py +202 -0
  88. quantized/calc/stats_anova_ext.py +338 -0
  89. quantized/calc/stats_dist.py +196 -0
  90. quantized/calc/stats_glm.py +245 -0
  91. quantized/calc/stats_multivar.py +289 -0
  92. quantized/calc/stats_roc.py +157 -0
  93. quantized/calc/stats_survival.py +261 -0
  94. quantized/calc/stats_tests.py +380 -0
  95. quantized/calc/substrates.py +181 -0
  96. quantized/calc/superconductor.py +359 -0
  97. quantized/calc/surface_fit.py +290 -0
  98. quantized/calc/surface_models.py +156 -0
  99. quantized/calc/thermal.py +119 -0
  100. quantized/calc/thin_film.py +425 -0
  101. quantized/calc/unit_convert.py +259 -0
  102. quantized/calc/units.py +80 -0
  103. quantized/calc/vacuum.py +290 -0
  104. quantized/calc/xray.py +169 -0
  105. quantized/cli.py +214 -0
  106. quantized/datastruct.py +153 -0
  107. quantized/io/__init__.py +11 -0
  108. quantized/io/_hdf5_layout.py +308 -0
  109. quantized/io/_jcamp_asdf.py +135 -0
  110. quantized/io/_xrdml_scan.py +291 -0
  111. quantized/io/base.py +82 -0
  112. quantized/io/bruker_brml.py +177 -0
  113. quantized/io/bruker_raw.py +158 -0
  114. quantized/io/cif.py +266 -0
  115. quantized/io/consolidated.py +122 -0
  116. quantized/io/delimited.py +222 -0
  117. quantized/io/excel.py +135 -0
  118. quantized/io/hdf5.py +192 -0
  119. quantized/io/import_filters.py +178 -0
  120. quantized/io/import_preview.py +262 -0
  121. quantized/io/jcamp.py +179 -0
  122. quantized/io/lakeshore.py +163 -0
  123. quantized/io/ncnr.py +278 -0
  124. quantized/io/netcdf.py +195 -0
  125. quantized/io/opus.py +231 -0
  126. quantized/io/origin.py +346 -0
  127. quantized/io/origin_com.py +194 -0
  128. quantized/io/origin_project/__init__.py +221 -0
  129. quantized/io/origin_project/annotation_marks.py +288 -0
  130. quantized/io/origin_project/container.py +262 -0
  131. quantized/io/origin_project/curve_style_color.py +359 -0
  132. quantized/io/origin_project/figure_geometry.py +108 -0
  133. quantized/io/origin_project/figure_layers.py +333 -0
  134. quantized/io/origin_project/figure_text.py +258 -0
  135. quantized/io/origin_project/figures.py +210 -0
  136. quantized/io/origin_project/figures_opju.py +440 -0
  137. quantized/io/origin_project/notes.py +302 -0
  138. quantized/io/origin_project/opj.py +459 -0
  139. quantized/io/origin_project/opj_curves.py +297 -0
  140. quantized/io/origin_project/opj_shapes.py +148 -0
  141. quantized/io/origin_project/opju.py +146 -0
  142. quantized/io/origin_project/opju_axis_real_form.py +418 -0
  143. quantized/io/origin_project/opju_axis_specimen_form.py +167 -0
  144. quantized/io/origin_project/opju_codec.py +370 -0
  145. quantized/io/origin_project/opju_curves.py +497 -0
  146. quantized/io/origin_project/opju_curves_allcols.py +258 -0
  147. quantized/io/origin_project/opju_figure_curves.py +302 -0
  148. quantized/io/origin_project/opju_figure_text.py +245 -0
  149. quantized/io/origin_project/opju_reports.py +129 -0
  150. quantized/io/origin_project/origin_richtext.py +145 -0
  151. quantized/io/origin_project/preview.py +132 -0
  152. quantized/io/origin_project/templates.py +314 -0
  153. quantized/io/origin_project/tree.py +379 -0
  154. quantized/io/origin_project/tree_opju.py +228 -0
  155. quantized/io/origin_project/windows.py +238 -0
  156. quantized/io/origin_project/windows_opju.py +393 -0
  157. quantized/io/origin_project/writer.py +156 -0
  158. quantized/io/origin_project/writer_blocks.py +282 -0
  159. quantized/io/qd.py +380 -0
  160. quantized/io/refl1d.py +132 -0
  161. quantized/io/registry.py +210 -0
  162. quantized/io/report_export.py +347 -0
  163. quantized/io/rigaku.py +100 -0
  164. quantized/io/sims.py +398 -0
  165. quantized/io/spc.py +311 -0
  166. quantized/io/xrd_csv.py +308 -0
  167. quantized/io/xrdml.py +394 -0
  168. quantized/jobs.py +173 -0
  169. quantized/plugins/__init__.py +50 -0
  170. quantized/plugins/contract.py +111 -0
  171. quantized/plugins/loader.py +394 -0
  172. quantized/plugins/steps.py +90 -0
  173. quantized/routes/__init__.py +7 -0
  174. quantized/routes/_bookcache.py +62 -0
  175. quantized/routes/_export_common.py +27 -0
  176. quantized/routes/_payload.py +58 -0
  177. quantized/routes/_uploadcache.py +59 -0
  178. quantized/routes/aggregate.py +46 -0
  179. quantized/routes/baseline.py +210 -0
  180. quantized/routes/books.py +117 -0
  181. quantized/routes/calc.py +56 -0
  182. quantized/routes/corrections.py +78 -0
  183. quantized/routes/crystallography.py +80 -0
  184. quantized/routes/diffusion.py +58 -0
  185. quantized/routes/electrical.py +101 -0
  186. quantized/routes/electrochemistry.py +83 -0
  187. quantized/routes/export.py +280 -0
  188. quantized/routes/export_facets.py +83 -0
  189. quantized/routes/export_figures.py +471 -0
  190. quantized/routes/export_page.py +125 -0
  191. quantized/routes/fitting.py +379 -0
  192. quantized/routes/fitting_bumps.py +97 -0
  193. quantized/routes/import_template.py +97 -0
  194. quantized/routes/import_wizard.py +150 -0
  195. quantized/routes/jobs_api.py +59 -0
  196. quantized/routes/magnetic.py +135 -0
  197. quantized/routes/magnetometry.py +133 -0
  198. quantized/routes/optics.py +98 -0
  199. quantized/routes/parsers.py +281 -0
  200. quantized/routes/peaks.py +184 -0
  201. quantized/routes/plot.py +103 -0
  202. quantized/routes/reductions.py +121 -0
  203. quantized/routes/reference.py +58 -0
  204. quantized/routes/reflectivity.py +91 -0
  205. quantized/routes/report_export.py +119 -0
  206. quantized/routes/rsm.py +136 -0
  207. quantized/routes/samples.py +32 -0
  208. quantized/routes/semiconductor.py +207 -0
  209. quantized/routes/sld.py +42 -0
  210. quantized/routes/spectral.py +54 -0
  211. quantized/routes/statplots.py +99 -0
  212. quantized/routes/stats.py +418 -0
  213. quantized/routes/stats_design.py +321 -0
  214. quantized/routes/substrates.py +46 -0
  215. quantized/routes/superconductor.py +139 -0
  216. quantized/routes/thermal.py +57 -0
  217. quantized/routes/thin_film.py +153 -0
  218. quantized/routes/vacuum.py +113 -0
  219. quantized/routes/xray.py +32 -0
  220. quantized/samples/demo_vsm.csv +42 -0
  221. quantized/server_launch.py +251 -0
  222. quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
  223. quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
  224. quantized/web/assets/index-BHmmCL-x.js +27 -0
  225. quantized/web/assets/index-BiZzN7J6.css +1 -0
  226. quantized/web/index.html +13 -0
  227. quantized/web/loading.html +69 -0
  228. quantized_lab-0.8.0.dist-info/METADATA +122 -0
  229. quantized_lab-0.8.0.dist-info/RECORD +233 -0
  230. quantized_lab-0.8.0.dist-info/WHEEL +4 -0
  231. quantized_lab-0.8.0.dist-info/entry_points.txt +4 -0
  232. quantized_lab-0.8.0.dist-info/licenses/LICENSE +201 -0
  233. 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)