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,181 @@
1
+ """Substrate property database + lattice-mismatch calculator.
2
+
3
+ Ports the MATLAB ``+calc/+substrates`` package (``listSubstrates`` /
4
+ ``getSubstrate``) and the epitaxial-mismatch formula from
5
+ ``+calc/+crystal/latticeMismatch.m``. Pure calc layer — no fastapi / pydantic
6
+ imports; data in → result dicts out.
7
+
8
+ The substrate table is a curated reference of common single-crystal oxide and
9
+ semiconductor substrates with their room-temperature lattice parameters
10
+ (Angstrom), coefficient of thermal expansion (CTE, 1e-6/K), relative
11
+ permittivity ``eps_r``, mass density (g/cm³) and lattice type. Values are
12
+ ported **verbatim** from the MATLAB ``getSubstrate`` table (the behavioural
13
+ reference) — do not "fix" them.
14
+
15
+ Lattice convention follows MATLAB ``getSubstrate``:
16
+ - ``cubic`` → a = b = c, α = β = γ = 90°
17
+ - ``hexagonal`` → a = b, c distinct, α = β = 90°, γ = 120°
18
+ - ``amorphous`` → no lattice (a/b/c and angles are ``None``)
19
+
20
+ Lattice mismatch (``latticeMismatch``):
21
+
22
+ .. math::
23
+
24
+ f = \frac{a_\text{film} - a_\text{sub}}{a_\text{sub}}
25
+
26
+ Positive ``f`` (film larger) → biaxial tension; negative → biaxial
27
+ compression; |f| ≤ 1e-6 → matched.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from typing import Any
33
+
34
+ __all__ = [
35
+ "get_substrate",
36
+ "lattice_mismatch",
37
+ "list_substrates",
38
+ "substrate_table",
39
+ ]
40
+
41
+ # Raw table ported verbatim from MATLAB getSubstrate.BuildTable():
42
+ # (name, formula, orientation, a, c, CTE[1e-6/K], eps_r, density[g/cm^3], type)
43
+ # c is None for cubic (set to a); a/c None for amorphous. DO NOT "fix" values.
44
+ _RAW: list[tuple[str, str, str, float | None, float | None, float, float, float, str]] = [
45
+ ("Si(100)", "Si", "(100)", 5.431, None, 2.6, 11.7, 2.329, "cubic"),
46
+ ("Si(111)", "Si", "(111)", 5.431, None, 2.6, 11.7, 2.329, "cubic"),
47
+ ("SiO2/Si", "SiO2", "amorphous", None, None, 0.5, 3.9, 2.20, "amorphous"),
48
+ ("Al2O3(0001)", "Al2O3", "(0001)", 4.758, 12.991, 5.0, 9.0, 3.987, "hexagonal"),
49
+ ("Al2O3(11-20)", "Al2O3", "(11-20)", 4.758, 12.991, 5.0, 9.0, 3.987, "hexagonal"),
50
+ ("MgO(100)", "MgO", "(100)", 4.212, None, 10.5, 9.8, 3.585, "cubic"),
51
+ ("SrTiO3(100)", "SrTiO3", "(100)", 3.905, None, 11.0, 300.0, 5.117, "cubic"),
52
+ ("GaAs(100)", "GaAs", "(100)", 5.653, None, 5.73, 12.9, 5.317, "cubic"),
53
+ ("LaAlO3(100)", "LaAlO3", "(100)", 3.789, None, 10.0, 24.0, 6.52, "cubic"),
54
+ ("LSAT(100)", "LSAT", "(100)", 3.868, None, 10.0, 22.0, 6.74, "cubic"),
55
+ ("Ge(100)", "Ge", "(100)", 5.658, None, 5.9, 16.0, 5.323, "cubic"),
56
+ ("InP(100)", "InP", "(100)", 5.869, None, 4.6, 12.5, 4.81, "cubic"),
57
+ ("YSZ(100)", "YSZ", "(100)", 5.125, None, 10.5, 27.0, 5.96, "cubic"),
58
+ ("MgAl2O4(100)", "MgAl2O4", "(100)", 8.083, None, 7.45, 8.1, 3.578, "cubic"),
59
+ ]
60
+
61
+
62
+ def _build_row(
63
+ name: str,
64
+ formula: str,
65
+ orientation: str,
66
+ a_val: float | None,
67
+ c_val: float | None,
68
+ cte: float,
69
+ eps_r: float,
70
+ density: float,
71
+ lattice_type: str,
72
+ ) -> dict[str, Any]:
73
+ """Expand one raw tuple into a full substrate dict (MATLAB BuildTable logic)."""
74
+ if lattice_type == "cubic":
75
+ a_out: float | None = a_val
76
+ b_out: float | None = a_val
77
+ c_out: float | None = a_val
78
+ alpha: float | None = 90.0
79
+ beta: float | None = 90.0
80
+ gamma: float | None = 90.0
81
+ elif lattice_type == "hexagonal":
82
+ a_out = a_val
83
+ b_out = a_val
84
+ c_out = c_val
85
+ alpha, beta, gamma = 90.0, 90.0, 120.0
86
+ elif lattice_type == "amorphous":
87
+ a_out = b_out = c_out = None
88
+ alpha = beta = gamma = None
89
+ else: # pragma: no cover - guarded by the static table
90
+ raise ValueError(f'Unrecognised lattice type "{lattice_type}" in substrate table.')
91
+ return {
92
+ "name": name,
93
+ "formula": formula,
94
+ "orientation": orientation,
95
+ "a": a_out,
96
+ "b": b_out,
97
+ "c": c_out,
98
+ "alpha": alpha,
99
+ "beta": beta,
100
+ "gamma": gamma,
101
+ "thermalExpansion": cte,
102
+ "dielectric": eps_r,
103
+ "density": density,
104
+ "latticeType": lattice_type,
105
+ }
106
+
107
+
108
+ _TABLE: list[dict[str, Any]] | None = None
109
+
110
+
111
+ def substrate_table() -> list[dict[str, Any]]:
112
+ """Return the full substrate table (list of property dicts), built once."""
113
+ global _TABLE
114
+ if _TABLE is None:
115
+ _TABLE = [_build_row(*row) for row in _RAW]
116
+ return _TABLE
117
+
118
+
119
+ def list_substrates() -> list[str]:
120
+ """Return the canonical list of substrate names (MATLAB ``listSubstrates``)."""
121
+ return [row["name"] for row in substrate_table()]
122
+
123
+
124
+ def _closest_name(name: str) -> str:
125
+ """Suggest the closest known name by shared-character overlap (MATLAB logic)."""
126
+ name_low = set(name.lower())
127
+ best = ""
128
+ best_score = -1
129
+ for row in substrate_table():
130
+ score = sum(1 for ch in row["name"].lower() if ch in name_low)
131
+ if score > best_score:
132
+ best_score = score
133
+ best = row["name"]
134
+ return best
135
+
136
+
137
+ def get_substrate(name: str) -> dict[str, Any]:
138
+ """Return the property dict for a named substrate (case-insensitive).
139
+
140
+ Mirrors MATLAB ``calc.substrates.getSubstrate``: exact case-insensitive
141
+ match; on miss, raises with the closest-by-character-overlap suggestion.
142
+
143
+ >>> get_substrate("SrTiO3(100)")["a"]
144
+ 3.905
145
+ >>> get_substrate("si(100)")["latticeType"]
146
+ 'cubic'
147
+ """
148
+ for row in substrate_table():
149
+ if row["name"].lower() == name.lower():
150
+ return row
151
+ suggestion = _closest_name(name)
152
+ raise ValueError(f'Unknown substrate "{name}". Did you mean "{suggestion}"?')
153
+
154
+
155
+ def lattice_mismatch(a_film: float, a_sub: float) -> dict[str, Any]:
156
+ """Epitaxial lattice mismatch f = (a_film - a_sub)/a_sub.
157
+
158
+ Ports ``calc.crystal.latticeMismatch``. Positive ``f`` → film under biaxial
159
+ tension; negative → compression; |f| ≤ 1e-6 → matched.
160
+
161
+ Args:
162
+ a_film: in-plane film lattice parameter (Å), > 0.
163
+ a_sub: substrate lattice parameter (Å), > 0.
164
+
165
+ >>> r = lattice_mismatch(3.876, 3.905)
166
+ >>> round(r["mismatchPct"], 4)
167
+ -0.7426
168
+ >>> r["description"]
169
+ 'compressive'
170
+ """
171
+ if a_film <= 0 or a_sub <= 0:
172
+ raise ValueError("a_film and a_sub must be positive")
173
+ f = (a_film - a_sub) / a_sub
174
+ f_pct = f * 100.0
175
+ if f > 1e-6:
176
+ desc = "tensile"
177
+ elif f < -1e-6:
178
+ desc = "compressive"
179
+ else:
180
+ desc = "matched"
181
+ return {"mismatch": f, "mismatchPct": f_pct, "description": desc}
@@ -0,0 +1,359 @@
1
+ r"""Superconductivity calculators (DiraCulator ``buildSuperconductorTab`` +
2
+ ``+calc/+superconductor``).
3
+
4
+ Pure calc layer — closed-form scalars in, result dicts out. No fastapi /
5
+ pydantic imports. Ports the scalar MATLAB ``calc.superconductor`` functions
6
+ verbatim (London depth, coherence length, GL parameter, critical fields,
7
+ depairing current) plus the material-preset table and the weak-coupling BCS
8
+ gap relation.
9
+
10
+ .. math::
11
+
12
+ \lambda(T) = \frac{\lambda_0}{\sqrt{1 - (T/T_c)^4}} \qquad
13
+ \xi(T) = \frac{\xi_0}{\sqrt{1 - (T/T_c)^2}} \\
14
+ \kappa = \lambda/\xi \qquad
15
+ H_c(T) = H_{c0}\,(1 - (T/T_c)^2) \\
16
+ H_{c1} = \frac{\Phi_0}{4\pi\lambda^2}\,(\ln\kappa + 0.5) \qquad
17
+ H_{c2} = \frac{\Phi_0}{2\pi\xi^2} \\
18
+ J_d(T) = \frac{H_c(T)}{3\sqrt{6}\,\pi\,\lambda(T)} \qquad
19
+ \Delta_0 = 1.764\,k_B T_c
20
+
21
+ Conventions follow the MATLAB toolbox (the behavioural reference): lengths in
22
+ nm, temperatures in K, fields in Oe (Gaussian CGS, ``Φ₀`` converted to G·cm²),
23
+ current density in A/cm², gap in meV. ``type`` is ``'I'`` for
24
+ ``κ < 1/√2 ≈ 0.7071`` and ``'II'`` otherwise.
25
+
26
+ Reference values (from the MATLAB docstrings / closed form):
27
+ - ``london_depth(39, 4.2, 9.25) -> lambda ≈ 39.86`` nm (Nb)
28
+ - ``coherence_length(38, 4.2, 9.25) -> xi ≈ 42.65`` nm (Nb)
29
+ - ``gl_parameter(39, 38) -> kappa ≈ 1.026`` (Nb, type II)
30
+ - ``critical_fields(1980, 9.25, 4.2) Nb -> type II, Hc1 < Hc < Hc2``
31
+ - ``bcs_gap(9.25) -> delta0 ≈ 1.406 meV, ratio = 3.528`` (weak coupling)
32
+
33
+ The three array-based data-reduction routines in the MATLAB package
34
+ (``bcsGap`` curve fitting, ``extractTc`` from R(T), ``beanJc`` from M(H)) are
35
+ measurement-analysis functions, not calculator-tab features, and are deferred
36
+ to the transport / magnetometry analysis surface.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import math
42
+ from typing import Any
43
+
44
+ from quantized.calc.constants import constants
45
+
46
+ __all__ = [
47
+ "bcs_gap",
48
+ "coherence_length",
49
+ "critical_fields",
50
+ "depairing_current",
51
+ "gl_parameter",
52
+ "london_depth",
53
+ "material_presets",
54
+ ]
55
+
56
+ # κ boundary between type-I and type-II superconductors.
57
+ _KAPPA_BOUNDARY = 1.0 / math.sqrt(2.0) # ≈ 0.7071
58
+
59
+ # Weak-coupling BCS: 2*Delta0/(kB*Tc) = 3.528 -> Delta0 = 1.764*kB*Tc.
60
+ _BCS_RATIO = 3.528
61
+ _BCS_HALF = _BCS_RATIO / 2.0 # 1.764
62
+
63
+ # Material presets (Tinkham 2nd ed.; Orlando & Delin). lambda0/xi0 in nm,
64
+ # Hc0 in Oe, Delta0 in meV. Port verbatim from materialPresets.m.
65
+ _PRESETS: dict[str, dict[str, Any]] = {
66
+ "Nb": {"Tc": 9.25, "lambda0": 39.0, "xi0": 38.0, "Hc0": 1980.0, "Delta0": 1.55, "type": "II"},
67
+ "NbN": {"Tc": 16.0, "lambda0": 200.0, "xi0": 5.0, "Hc0": 80000.0, "Delta0": 2.6, "type": "II"},
68
+ "YBCO": {"Tc": 92.0, "lambda0": 150.0, "xi0": 1.5, "Hc0": 0.0, "Delta0": 20.0, "type": "II"},
69
+ "MgB2": {"Tc": 39.0, "lambda0": 140.0, "xi0": 5.0, "Hc0": 0.0, "Delta0": 7.1, "type": "II"},
70
+ "Al": {"Tc": 1.18, "lambda0": 16.0, "xi0": 1600.0, "Hc0": 105.0, "Delta0": 0.172, "type": "I"},
71
+ "Pb": {"Tc": 7.19, "lambda0": 37.0, "xi0": 83.0, "Hc0": 803.0, "Delta0": 1.33, "type": "I"},
72
+ "In": {"Tc": 3.41, "lambda0": 24.0, "xi0": 440.0, "Hc0": 282.0, "Delta0": 0.541, "type": "I"},
73
+ "Sn": {"Tc": 3.72, "lambda0": 34.0, "xi0": 230.0, "Hc0": 305.0, "Delta0": 0.592, "type": "I"},
74
+ }
75
+
76
+
77
+ def material_presets(material: str | None = None) -> dict[str, Any]:
78
+ """Superconductor material property table (``materialPresets.m``).
79
+
80
+ Returns the full table keyed by material name when ``material`` is omitted,
81
+ or a single material's properties (``Tc``, ``lambda0``, ``xi0``, ``Hc0``,
82
+ ``Delta0``, ``type``) when given. Names are matched case-insensitively.
83
+
84
+ >>> material_presets("Nb")["Tc"]
85
+ 9.25
86
+ >>> sorted(material_presets()["materials"]) # doctest: +ELLIPSIS
87
+ ['Al', 'In', 'MgB2', 'Nb', 'NbN', 'Pb', 'Sn', 'YBCO']
88
+ """
89
+ if material is None or material == "":
90
+ return {"materials": {k: dict(v) for k, v in _PRESETS.items()}}
91
+ key = _match_material(material)
92
+ return dict(_PRESETS[key])
93
+
94
+
95
+ def _match_material(material: str) -> str:
96
+ for name in _PRESETS:
97
+ if name.lower() == material.lower():
98
+ return name
99
+ valid = ", ".join(_PRESETS)
100
+ raise ValueError(f"Unknown material '{material}'. Valid options: {valid}.")
101
+
102
+
103
+ def _resolve(
104
+ explicit: float | None, material: str | None, field: str, label: str
105
+ ) -> float:
106
+ """Explicit value if given, else the preset field, else an error."""
107
+ if explicit is not None:
108
+ return explicit
109
+ if material:
110
+ return float(_PRESETS[_match_material(material)][field])
111
+ raise ValueError(f"Provide '{label}' or a Material name.")
112
+
113
+
114
+ def _check_below_tc(t: float, tc: float) -> None:
115
+ if tc <= 0:
116
+ raise ValueError("Tc must be positive")
117
+ if t < 0:
118
+ raise ValueError("T must be non-negative")
119
+ if t >= tc:
120
+ raise ValueError(f"T ({t:.4g} K) must be below Tc ({tc:.4g} K).")
121
+
122
+
123
+ def london_depth(
124
+ lambda0: float | None = None,
125
+ t: float = 0.0,
126
+ tc: float | None = None,
127
+ *,
128
+ material: str | None = None,
129
+ ) -> dict[str, float]:
130
+ """London penetration depth at temperature T (``londonDepth.m``).
131
+
132
+ Two-fluid (Gorter-Casimir) approximation
133
+ ``λ(T) = λ₀ / √(1 − (T/T_c)⁴)``.
134
+
135
+ Args:
136
+ lambda0: zero-temperature London depth λ₀ (nm); from preset if omitted.
137
+ t: measurement temperature (K), 0 ≤ t < Tc.
138
+ tc: critical temperature (K); from preset if omitted.
139
+ material: optional preset name supplying λ₀ and Tc.
140
+
141
+ >>> round(london_depth(39.0, 4.2, 9.25)["lambda"], 2)
142
+ 39.86
143
+ """
144
+ lam0 = _resolve(lambda0, material, "lambda0", "lambda0")
145
+ tc_v = _resolve(tc, material, "Tc", "Tc")
146
+ if lam0 <= 0:
147
+ raise ValueError("lambda0 must be positive")
148
+ _check_below_tc(t, tc_v)
149
+ lam = lam0 / math.sqrt(1.0 - (t / tc_v) ** 4)
150
+ return {"lambda": lam, "lambda0": lam0, "T": t, "Tc": tc_v}
151
+
152
+
153
+ def coherence_length(
154
+ xi0: float | None = None,
155
+ t: float = 0.0,
156
+ tc: float | None = None,
157
+ *,
158
+ material: str | None = None,
159
+ ) -> dict[str, float]:
160
+ """BCS coherence length at temperature T (``coherenceLength.m``).
161
+
162
+ Gorkov / GL-regime temperature dependence
163
+ ``ξ(T) = ξ₀ / √(1 − (T/T_c)²)``.
164
+
165
+ >>> round(coherence_length(38.0, 4.2, 9.25)["xi"], 2)
166
+ 42.65
167
+ """
168
+ xi0_v = _resolve(xi0, material, "xi0", "xi0")
169
+ tc_v = _resolve(tc, material, "Tc", "Tc")
170
+ if xi0_v <= 0:
171
+ raise ValueError("xi0 must be positive")
172
+ _check_below_tc(t, tc_v)
173
+ xi = xi0_v / math.sqrt(1.0 - (t / tc_v) ** 2)
174
+ return {"xi": xi, "xi0": xi0_v, "T": t, "Tc": tc_v}
175
+
176
+
177
+ def _sc_type(kappa: float) -> str:
178
+ return "I" if kappa < _KAPPA_BOUNDARY else "II"
179
+
180
+
181
+ def gl_parameter(
182
+ lambda_: float | None = None,
183
+ xi: float | None = None,
184
+ *,
185
+ material: str | None = None,
186
+ t: float | None = None,
187
+ ) -> dict[str, Any]:
188
+ """Ginzburg-Landau parameter κ = λ/ξ (``glParameter.m``).
189
+
190
+ Type-I when ``κ < 1/√2 ≈ 0.7071``, type-II otherwise. When ``material``
191
+ and ``t`` are given, λ and ξ are computed at T from the preset.
192
+
193
+ >>> r = gl_parameter(39.0, 38.0)
194
+ >>> round(r["kappa"], 4), r["type"]
195
+ (1.0263, 'II')
196
+ """
197
+ lam = lambda_
198
+ xi_v = xi
199
+ if material:
200
+ if t is None:
201
+ raise ValueError("Provide T when using Material for glParameter.")
202
+ if lam is None:
203
+ lam = london_depth(t=t, material=material)["lambda"]
204
+ if xi_v is None:
205
+ xi_v = coherence_length(t=t, material=material)["xi"]
206
+ if lam is None or xi_v is None:
207
+ raise ValueError("Provide both lambda and xi, or a Material name with T.")
208
+ if lam <= 0 or xi_v <= 0:
209
+ raise ValueError("lambda and xi must be positive")
210
+ kappa = lam / xi_v
211
+ return {"kappa": kappa, "lambda": lam, "xi": xi_v, "type": _sc_type(kappa)}
212
+
213
+
214
+ def critical_fields(
215
+ hc0: float | None = None,
216
+ tc: float | None = None,
217
+ t: float = 0.0,
218
+ *,
219
+ material: str | None = None,
220
+ lambda_: float | None = None,
221
+ xi: float | None = None,
222
+ kappa: float | None = None,
223
+ ) -> dict[str, Any]:
224
+ """Superconducting critical fields at temperature T (``criticalFields.m``).
225
+
226
+ Thermodynamic ``Hc(T) = Hc0·(1 − (T/T_c)²)`` for both types. For type-II
227
+ (preset type ``'II'``, ``Hc0`` zero/absent, or explicit λ+ξ / κ):
228
+
229
+ - ``Hc1 = (Φ₀/(4πλ²))·(ln κ + 0.5)`` (Tinkham Eq. 5.11, NaN for type-I),
230
+ - ``Hc2 = Φ₀/(2πξ²)``,
231
+
232
+ with Φ₀ in G·cm² and λ, ξ in cm. All fields in Oe.
233
+
234
+ Args:
235
+ hc0: thermodynamic critical field at T=0 (Oe); from preset if omitted.
236
+ tc: critical temperature (K); from preset if omitted.
237
+ t: measurement temperature (K), 0 ≤ t < Tc.
238
+ material: optional preset name; supplies Hc0, Tc, type, and λ/ξ at T.
239
+ lambda_, xi: λ(T), ξ(T) in nm (override / direct type-II input).
240
+ kappa: GL parameter override (else λ/ξ).
241
+
242
+ >>> r = critical_fields(material="Nb", t=4.2)
243
+ >>> r["type"], r["Hc1"] < r["Hc"] < r["Hc2"]
244
+ ('II', True)
245
+ """
246
+ if material:
247
+ preset = material_presets(material)
248
+ hc0_v = hc0 if hc0 is not None else float(preset["Hc0"])
249
+ tc_v = tc if tc is not None else float(preset["Tc"])
250
+ sc_type: str = str(preset["type"])
251
+ else:
252
+ if hc0 is None or tc is None:
253
+ raise ValueError("Provide Hc0 and Tc, or a Material name.")
254
+ hc0_v = hc0
255
+ tc_v = tc
256
+ sc_type = ""
257
+ _check_below_tc(t, tc_v)
258
+
259
+ t_red = t / tc_v
260
+ hc = hc0_v * (1.0 - t_red**2)
261
+
262
+ phi0_gcm2 = constants()["Phi0"] * 1e8 # Wb -> G*cm^2 (= 2.0678e-7)
263
+ hc1 = math.nan
264
+ hc2 = math.nan
265
+
266
+ has_type_ii = (lambda_ is not None and xi is not None) or kappa is not None
267
+ if sc_type == "II" or hc0_v == 0 or has_type_ii:
268
+ lam = lambda_
269
+ xi_v = xi
270
+ kap = kappa
271
+ if lam is None and material:
272
+ lam = london_depth(t=t, material=material)["lambda"]
273
+ if xi_v is None and material:
274
+ xi_v = coherence_length(t=t, material=material)["xi"]
275
+ if kap is None and lam is not None and xi_v is not None:
276
+ kap = lam / xi_v
277
+
278
+ if lam is not None and xi_v is not None and kap is not None:
279
+ lam_cm = lam * 1e-7
280
+ xi_cm = xi_v * 1e-7
281
+ if kap > _KAPPA_BOUNDARY:
282
+ hc1 = phi0_gcm2 * (math.log(kap) + 0.5) / (4 * math.pi * lam_cm**2)
283
+ hc2 = phi0_gcm2 / (2 * math.pi * xi_cm**2)
284
+ if not sc_type:
285
+ sc_type = "II"
286
+
287
+ if not sc_type:
288
+ sc_type = "I"
289
+
290
+ return {"Hc": hc, "Hc1": hc1, "Hc2": hc2, "type": sc_type, "T": t, "Tc": tc_v}
291
+
292
+
293
+ def depairing_current(
294
+ hc0: float | None = None,
295
+ lambda0: float | None = None,
296
+ tc: float | None = None,
297
+ t: float = 0.0,
298
+ *,
299
+ material: str | None = None,
300
+ ) -> dict[str, float]:
301
+ """Depairing (pair-breaking) current density (``depairingCurrent.m``).
302
+
303
+ ``Jd(T) = Hc(T) / (3√6·π·λ(T))`` in Gaussian CGS, converted to A/cm² via
304
+ ``1 Oe/cm = (10³/4π) A/cm²``. Hc(T) from :func:`critical_fields`, λ(T) from
305
+ :func:`london_depth`.
306
+
307
+ >>> r = depairing_current(1980.0, 39.0, 9.25, 4.2)
308
+ >>> r["JdMA"] > 0
309
+ True
310
+ """
311
+ hc0_v = _resolve(hc0, material, "Hc0", "Hc0")
312
+ lam0 = _resolve(lambda0, material, "lambda0", "lambda0")
313
+ tc_v = _resolve(tc, material, "Tc", "Tc")
314
+ _check_below_tc(t, tc_v)
315
+
316
+ if material:
317
+ hc_t = critical_fields(material=material, t=t)["Hc"]
318
+ lam_t = london_depth(t=t, material=material)["lambda"]
319
+ else:
320
+ hc_t = critical_fields(hc0=hc0_v, tc=tc_v, t=t)["Hc"]
321
+ lam_t = london_depth(lambda0=lam0, t=t, tc=tc_v)["lambda"]
322
+
323
+ lam_cm = lam_t * 1e-7
324
+ jd_cgs = hc_t / (3 * math.sqrt(6) * math.pi * lam_cm)
325
+ jd_acm2 = jd_cgs * (1e3 / (4 * math.pi))
326
+ return {"Jd": jd_acm2, "JdMA": jd_acm2 * 1e-6, "T": t, "Tc": tc_v}
327
+
328
+
329
+ def bcs_gap(tc: float, t: float | None = None) -> dict[str, float]:
330
+ """Weak-coupling BCS energy gap from Tc.
331
+
332
+ Zero-temperature gap ``Δ₀ = 1.764·k_B·T_c`` (the weak-coupling relation
333
+ ``2Δ₀/(k_B T_c) = 3.528``). When ``t`` is given (0 < t < Tc) the
334
+ Mühlschlegel approximation ``Δ(T) = Δ₀·tanh(1.74·√(T_c/T − 1))`` is also
335
+ returned. Gap values in meV.
336
+
337
+ >>> r = bcs_gap(9.25)
338
+ >>> round(r["delta0"], 3), round(r["ratio"], 3)
339
+ (1.406, 3.528)
340
+ """
341
+ if tc <= 0:
342
+ raise ValueError("Tc must be positive")
343
+ kb_mev = constants()["kB"] / constants()["e"] * 1e3 # meV/K
344
+ delta0 = _BCS_HALF * kb_mev * tc
345
+ delta_t = delta0
346
+ if t is not None:
347
+ if t < 0:
348
+ raise ValueError("T must be non-negative")
349
+ if t == 0 or t >= tc:
350
+ delta_t = 0.0 if t >= tc else delta0
351
+ else:
352
+ delta_t = delta0 * math.tanh(1.74 * math.sqrt(tc / t - 1.0))
353
+ return {
354
+ "delta0": delta0,
355
+ "ratio": _BCS_RATIO,
356
+ "deltaT": delta_t,
357
+ "Tc": tc,
358
+ "T": t if t is not None else 0.0,
359
+ }