reaxkit 1.0.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 (130) hide show
  1. reaxkit/__init__.py +0 -0
  2. reaxkit/analysis/__init__.py +0 -0
  3. reaxkit/analysis/composed/RDF_analyzer.py +560 -0
  4. reaxkit/analysis/composed/__init__.py +0 -0
  5. reaxkit/analysis/composed/connectivity_analyzer.py +706 -0
  6. reaxkit/analysis/composed/coordination_analyzer.py +144 -0
  7. reaxkit/analysis/composed/electrostatics_analyzer.py +687 -0
  8. reaxkit/analysis/per_file/__init__.py +0 -0
  9. reaxkit/analysis/per_file/control_analyzer.py +165 -0
  10. reaxkit/analysis/per_file/eregime_analyzer.py +108 -0
  11. reaxkit/analysis/per_file/ffield_analyzer.py +305 -0
  12. reaxkit/analysis/per_file/fort13_analyzer.py +79 -0
  13. reaxkit/analysis/per_file/fort57_analyzer.py +106 -0
  14. reaxkit/analysis/per_file/fort73_analyzer.py +61 -0
  15. reaxkit/analysis/per_file/fort74_analyzer.py +65 -0
  16. reaxkit/analysis/per_file/fort76_analyzer.py +191 -0
  17. reaxkit/analysis/per_file/fort78_analyzer.py +154 -0
  18. reaxkit/analysis/per_file/fort79_analyzer.py +83 -0
  19. reaxkit/analysis/per_file/fort7_analyzer.py +393 -0
  20. reaxkit/analysis/per_file/fort99_analyzer.py +411 -0
  21. reaxkit/analysis/per_file/molfra_analyzer.py +359 -0
  22. reaxkit/analysis/per_file/params_analyzer.py +258 -0
  23. reaxkit/analysis/per_file/summary_analyzer.py +84 -0
  24. reaxkit/analysis/per_file/trainset_analyzer.py +84 -0
  25. reaxkit/analysis/per_file/vels_analyzer.py +95 -0
  26. reaxkit/analysis/per_file/xmolout_analyzer.py +528 -0
  27. reaxkit/cli.py +181 -0
  28. reaxkit/count_loc.py +276 -0
  29. reaxkit/data/alias.yaml +89 -0
  30. reaxkit/data/constants.yaml +27 -0
  31. reaxkit/data/reaxff_input_files_contents.yaml +186 -0
  32. reaxkit/data/reaxff_output_files_contents.yaml +301 -0
  33. reaxkit/data/units.yaml +38 -0
  34. reaxkit/help/__init__.py +0 -0
  35. reaxkit/help/help_index_loader.py +531 -0
  36. reaxkit/help/introspection_utils.py +131 -0
  37. reaxkit/io/__init__.py +0 -0
  38. reaxkit/io/base_handler.py +165 -0
  39. reaxkit/io/generators/__init__.py +0 -0
  40. reaxkit/io/generators/control_generator.py +123 -0
  41. reaxkit/io/generators/eregime_generator.py +341 -0
  42. reaxkit/io/generators/geo_generator.py +967 -0
  43. reaxkit/io/generators/trainset_generator.py +1758 -0
  44. reaxkit/io/generators/tregime_generator.py +113 -0
  45. reaxkit/io/generators/vregime_generator.py +164 -0
  46. reaxkit/io/generators/xmolout_generator.py +304 -0
  47. reaxkit/io/handlers/__init__.py +0 -0
  48. reaxkit/io/handlers/control_handler.py +209 -0
  49. reaxkit/io/handlers/eregime_handler.py +122 -0
  50. reaxkit/io/handlers/ffield_handler.py +812 -0
  51. reaxkit/io/handlers/fort13_handler.py +123 -0
  52. reaxkit/io/handlers/fort57_handler.py +143 -0
  53. reaxkit/io/handlers/fort73_handler.py +145 -0
  54. reaxkit/io/handlers/fort74_handler.py +155 -0
  55. reaxkit/io/handlers/fort76_handler.py +195 -0
  56. reaxkit/io/handlers/fort78_handler.py +142 -0
  57. reaxkit/io/handlers/fort79_handler.py +227 -0
  58. reaxkit/io/handlers/fort7_handler.py +264 -0
  59. reaxkit/io/handlers/fort99_handler.py +128 -0
  60. reaxkit/io/handlers/geo_handler.py +224 -0
  61. reaxkit/io/handlers/molfra_handler.py +184 -0
  62. reaxkit/io/handlers/params_handler.py +137 -0
  63. reaxkit/io/handlers/summary_handler.py +135 -0
  64. reaxkit/io/handlers/trainset_handler.py +658 -0
  65. reaxkit/io/handlers/vels_handler.py +293 -0
  66. reaxkit/io/handlers/xmolout_handler.py +174 -0
  67. reaxkit/utils/__init__.py +0 -0
  68. reaxkit/utils/alias.py +219 -0
  69. reaxkit/utils/cache.py +77 -0
  70. reaxkit/utils/constants.py +75 -0
  71. reaxkit/utils/equation_of_states.py +96 -0
  72. reaxkit/utils/exceptions.py +27 -0
  73. reaxkit/utils/frame_utils.py +175 -0
  74. reaxkit/utils/log.py +43 -0
  75. reaxkit/utils/media/__init__.py +0 -0
  76. reaxkit/utils/media/convert.py +90 -0
  77. reaxkit/utils/media/make_video.py +91 -0
  78. reaxkit/utils/media/plotter.py +812 -0
  79. reaxkit/utils/numerical/__init__.py +0 -0
  80. reaxkit/utils/numerical/extrema_finder.py +96 -0
  81. reaxkit/utils/numerical/moving_average.py +103 -0
  82. reaxkit/utils/numerical/numerical_calcs.py +75 -0
  83. reaxkit/utils/numerical/signal_ops.py +135 -0
  84. reaxkit/utils/path.py +55 -0
  85. reaxkit/utils/units.py +104 -0
  86. reaxkit/webui/__init__.py +0 -0
  87. reaxkit/webui/app.py +0 -0
  88. reaxkit/webui/components.py +0 -0
  89. reaxkit/webui/layouts.py +0 -0
  90. reaxkit/webui/utils.py +0 -0
  91. reaxkit/workflows/__init__.py +0 -0
  92. reaxkit/workflows/composed/__init__.py +0 -0
  93. reaxkit/workflows/composed/coordination_workflow.py +393 -0
  94. reaxkit/workflows/composed/electrostatics_workflow.py +587 -0
  95. reaxkit/workflows/composed/xmolout_fort7_workflow.py +343 -0
  96. reaxkit/workflows/meta/__init__.py +0 -0
  97. reaxkit/workflows/meta/help_workflow.py +136 -0
  98. reaxkit/workflows/meta/introspection_workflow.py +235 -0
  99. reaxkit/workflows/meta/make_video_workflow.py +61 -0
  100. reaxkit/workflows/meta/plotter_workflow.py +601 -0
  101. reaxkit/workflows/per_file/__init__.py +0 -0
  102. reaxkit/workflows/per_file/control_workflow.py +110 -0
  103. reaxkit/workflows/per_file/eregime_workflow.py +267 -0
  104. reaxkit/workflows/per_file/ffield_workflow.py +390 -0
  105. reaxkit/workflows/per_file/fort13_workflow.py +86 -0
  106. reaxkit/workflows/per_file/fort57_workflow.py +137 -0
  107. reaxkit/workflows/per_file/fort73_workflow.py +151 -0
  108. reaxkit/workflows/per_file/fort74_workflow.py +88 -0
  109. reaxkit/workflows/per_file/fort76_workflow.py +188 -0
  110. reaxkit/workflows/per_file/fort78_workflow.py +135 -0
  111. reaxkit/workflows/per_file/fort79_workflow.py +314 -0
  112. reaxkit/workflows/per_file/fort7_workflow.py +592 -0
  113. reaxkit/workflows/per_file/fort83_workflow.py +60 -0
  114. reaxkit/workflows/per_file/fort99_workflow.py +223 -0
  115. reaxkit/workflows/per_file/geo_workflow.py +554 -0
  116. reaxkit/workflows/per_file/molfra_workflow.py +577 -0
  117. reaxkit/workflows/per_file/params_workflow.py +135 -0
  118. reaxkit/workflows/per_file/summary_workflow.py +161 -0
  119. reaxkit/workflows/per_file/trainset_workflow.py +356 -0
  120. reaxkit/workflows/per_file/tregime_workflow.py +79 -0
  121. reaxkit/workflows/per_file/vels_workflow.py +309 -0
  122. reaxkit/workflows/per_file/vregime_workflow.py +75 -0
  123. reaxkit/workflows/per_file/xmolout_workflow.py +678 -0
  124. reaxkit-1.0.0.dist-info/METADATA +128 -0
  125. reaxkit-1.0.0.dist-info/RECORD +130 -0
  126. reaxkit-1.0.0.dist-info/WHEEL +5 -0
  127. reaxkit-1.0.0.dist-info/entry_points.txt +2 -0
  128. reaxkit-1.0.0.dist-info/licenses/AUTHORS.md +20 -0
  129. reaxkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  130. reaxkit-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,411 @@
1
+ """
2
+ fort.99 (training-set and EOS) analysis utilities.
3
+
4
+ This module provides helpers for analyzing ReaxFF training-set output
5
+ stored in ``fort.99`` files, including error inspection, two-body ENERGY
6
+ term parsing, and equation-of-state (EOS) analysis for bulk modulus
7
+ extraction.
8
+
9
+ Typical use cases include:
10
+
11
+ - inspecting QM–FF energy differences for training targets
12
+ - parsing pairwise ENERGY terms from fort.99 titles
13
+ - constructing energy–volume datasets using fort.74
14
+ - computing bulk modulus via a Vinet equation-of-state fit
15
+ """
16
+
17
+
18
+ from __future__ import annotations
19
+ import pandas as pd
20
+ import numpy as np
21
+
22
+ from reaxkit.io.base_handler import BaseHandler
23
+ from reaxkit.utils.constants import const
24
+ from reaxkit.utils.equation_of_states import vinet_energy_ev
25
+
26
+ def get_fort99_data(
27
+ handler: BaseHandler,
28
+ *,
29
+ sortby: str = "lineno",
30
+ ascending: bool = True
31
+ ) -> pd.DataFrame:
32
+ """
33
+ Retrieve fort.99 data and compute QM–FF energy differences.
34
+
35
+ A new column ``qm_ff_difference`` is added as:
36
+ ``qm_value - ffield_value``.
37
+
38
+ Works on
39
+ --------
40
+ Fort99Handler — ``fort.99``
41
+
42
+ Parameters
43
+ ----------
44
+ handler : TemplateHandler
45
+ Parsed ``fort.99`` handler.
46
+ sortby : str, default="lineno"
47
+ Column name to sort by (e.g. ``error``, ``lineno``).
48
+ ascending : bool, default=True
49
+ Sort order.
50
+
51
+ Returns
52
+ -------
53
+ pandas.DataFrame
54
+ fort.99 table including the additional ``qm_ff_difference`` column.
55
+
56
+ Examples
57
+ --------
58
+ >>> df = get_fort99_data(h, sortby="error", ascending=False)
59
+ """
60
+ df = handler.dataframe().copy()
61
+
62
+ # Add qm_ff_difference column
63
+ df["qm_ff_difference"] = df["qm_value"] - df["ffield_value"]
64
+
65
+ # Validate requested sort column
66
+ if sortby not in df.columns:
67
+ raise ValueError(
68
+ f"Invalid sort key: '{sortby}'. "
69
+ f"Available columns: {list(df.columns)}"
70
+ )
71
+
72
+ # Sort based on user choice
73
+ df = df.sort_values(sortby, ascending=ascending)
74
+
75
+ return df
76
+
77
+ # --------------------------------------------------------------------------------------------------
78
+ # getting the EOS data (i.e., rows where section = ENERGY and have 2 identifiers in their title
79
+ # --------------------------------------------------------------------------------------------------
80
+
81
+ def parse_fort99_two_body_energy_terms(handler: BaseHandler) -> pd.DataFrame:
82
+ """
83
+ Parse pairwise ENERGY terms from the ``ENERGY`` section of fort.99.
84
+
85
+ Only ENERGY titles containing exactly two ``/`` separators are kept,
86
+ corresponding to two-body interaction terms.
87
+
88
+ Works on
89
+ --------
90
+ Fort99Handler — ``fort.99``
91
+
92
+ Parameters
93
+ ----------
94
+ handler : TemplateHandler
95
+ Parsed ``fort.99`` handler.
96
+
97
+ Returns
98
+ -------
99
+ pandas.DataFrame
100
+ ENERGY rows augmented with parsed columns:
101
+ ``opt1``, ``iden1``, ``n1``, ``opt2``, ``iden2``, ``n2``.
102
+
103
+ Examples
104
+ --------
105
+ >>> df = parse_fort99_two_body_energy_terms(h)
106
+ """
107
+ import re
108
+ import numpy as np
109
+ import pandas as pd
110
+
111
+ df = handler.dataframe().copy()
112
+
113
+ # 1) Basic sanity check
114
+ if "section" not in df.columns or "title" not in df.columns:
115
+ raise KeyError("Expected 'section' and 'title' columns in fort.99 DataFrame.")
116
+
117
+ # 2) Restrict to ENERGY section (case-insensitive)
118
+ energy_df = df[df["section"].astype(str).str.upper() == "ENERGY"].copy()
119
+
120
+ # 3) Keep rows with *at least* 2 "/" (some triple-body lines may be truncated)
121
+ energy_df = energy_df[energy_df["title"].astype(str).str.count("/") == 2].copy()
122
+
123
+ # Regex:
124
+ # Energy +Zn_h2o-1_P2/1.00 -Zn_oh-1_P1/1.00 ...
125
+ #
126
+ # - allow any non-"/" chars in identifiers: [^/]+?
127
+ # - allow ints or floats for n1, n2: \d+(?:\.\d+)?
128
+ # - ignore case on "Energy"
129
+ pattern = re.compile(
130
+ r"^Energy\s+"
131
+ r"(?P<sign1>[+-])(?P<iden1>[^/]+?)\s*/\s*(?P<n1>\d+(?:\.\d+)?)\s+"
132
+ r"(?P<sign2>[+-])(?P<iden2>[^/]+?)\s*/\s*(?P<n2>\d+(?:\.\d+)?)",
133
+ flags=re.IGNORECASE,
134
+ )
135
+
136
+ def _parse_title(title: str) -> dict:
137
+ """Return parsed components or NaNs if it doesn't match."""
138
+ m = pattern.search(title)
139
+ if not m:
140
+ # Return NaNs so we can drop these rows later instead of raising
141
+ return {
142
+ "opt1": np.nan,
143
+ "iden1": np.nan,
144
+ "n1": np.nan,
145
+ "opt2": np.nan,
146
+ "iden2": np.nan,
147
+ "n2": np.nan,
148
+ }
149
+
150
+ g = m.groupdict()
151
+ return {
152
+ "opt1": 1 if g["sign1"] == "+" else -1,
153
+ "iden1": g["iden1"].strip(),
154
+ "n1": float(g["n1"]),
155
+ "opt2": 1 if g["sign2"] == "+" else -1,
156
+ "iden2": g["iden2"].strip(),
157
+ "n2": float(g["n2"]),
158
+ }
159
+
160
+ # Apply parser row-wise
161
+ parsed = energy_df["title"].astype(str).apply(_parse_title)
162
+ parsed_df = pd.DataFrame(list(parsed)) # list-of-dicts -> DataFrame
163
+
164
+ # Merge parsed columns into energy_df
165
+ energy_df = pd.concat([energy_df.reset_index(drop=True), parsed_df], axis=1)
166
+
167
+ # Drop rows where parsing failed (NaNs in iden1/iden2)
168
+ energy_df = energy_df.dropna(subset=["iden1", "iden2"])
169
+
170
+ # (Optional) if you no longer need the raw 'title' column, remove it:
171
+ # energy_df = energy_df.drop(columns=["title"])
172
+
173
+ return energy_df
174
+
175
+
176
+ def fort99_energy_vs_volume(
177
+ fort99_handler: BaseHandler,
178
+ fort74_handler: BaseHandler,
179
+ ) -> pd.DataFrame:
180
+ """
181
+ Construct an energy–volume table from fort.99 and fort.74 data.
182
+
183
+ Pairwise ENERGY terms from fort.99 are matched with corresponding
184
+ volumes extracted from fort.74 based on shared identifiers.
185
+
186
+ Works on
187
+ --------
188
+ Fort99Handler + Fort74Handler — ``fort.99`` + ``fort.74``
189
+
190
+ Parameters
191
+ ----------
192
+ fort99_handler : TemplateHandler
193
+ Parsed ``fort.99`` handler.
194
+ fort74_handler : TemplateHandler
195
+ Parsed ``fort.74`` handler providing volumes.
196
+
197
+ Returns
198
+ -------
199
+ pandas.DataFrame
200
+ Table with columns:
201
+ ``iden1``, ``iden2``, ``ffield_value``, ``qm_value``, ``V_iden2``.
202
+
203
+ Examples
204
+ --------
205
+ >>> df = fort99_energy_vs_volume(f99, f74)
206
+ """
207
+ from reaxkit.analysis.per_file import fort74_analyzer
208
+
209
+ # 1) Build ENERGY section with parsed two-body terms
210
+ energy_df = parse_fort99_two_body_energy_terms(fort99_handler)
211
+
212
+ if energy_df.empty:
213
+ return pd.DataFrame(
214
+ columns=["iden1", "iden2", "ffield_value", "qm_value", "V_iden2"]
215
+ )
216
+
217
+ # 2) Keep only iden1 values appearing more than once
218
+ repeated = energy_df.groupby("iden1").filter(lambda g: len(g) > 1).copy()
219
+
220
+ if repeated.empty:
221
+ return pd.DataFrame(
222
+ columns=["iden1", "iden2", "ffield_value", "qm_value", "V_iden2"]
223
+ )
224
+
225
+ # 🔥 FIX: remove rows where iden1 == iden2
226
+ repeated = repeated[repeated["iden1"] != repeated["iden2"]]
227
+
228
+ if repeated.empty:
229
+ return pd.DataFrame(
230
+ columns=["iden1", "iden2", "ffield_value", "qm_value", "V_iden2"]
231
+ )
232
+
233
+ # 3) Load fort.74 data and extract identifier → volume
234
+ fort74_df = fort74_analyzer.get_fort74_data(fort74_handler)
235
+
236
+ if (
237
+ fort74_df.empty
238
+ or "identifier" not in fort74_df.columns
239
+ or "V" not in fort74_df.columns
240
+ ):
241
+ return pd.DataFrame(
242
+ columns=["iden1", "iden2", "ffield_value", "qm_value", "V_iden2"]
243
+ )
244
+
245
+ vol_df = fort74_df[["identifier", "V"]].drop_duplicates()
246
+
247
+ # 4) Attach volume of iden2
248
+ merged = repeated.merge(
249
+ vol_df,
250
+ left_on="iden2",
251
+ right_on="identifier",
252
+ how="left",
253
+ )
254
+
255
+ # 5) Final output
256
+ out = merged[["iden1", "iden2", "ffield_value", "qm_value", "V"]].rename(
257
+ columns={"V": "V_iden2"}
258
+ )
259
+
260
+ return out.sort_values(["iden1", "iden2"]).reset_index(drop=True)
261
+
262
+
263
+ #################################################################################
264
+ # Finding the bulk modulus of a system using Rose–Vinet equation of state
265
+ #################################################################################
266
+
267
+ def get_fort99_bulk_modulus(
268
+ fort99_handler,
269
+ fort74_handler,
270
+ *,
271
+ iden: str,
272
+ source: str = "ffield", # "ffield" or "qm" which defines which source of energy data should be used
273
+ shift_min_to_zero: bool = True, # helps conditioning; doesn't change K0
274
+ flip_sign: bool = False,
275
+ dropna: bool = True,
276
+ ) -> dict:
277
+ """
278
+ Compute the bulk modulus using a Vinet EOS fit to E(V) data.
279
+
280
+ Energy–volume points are obtained from pairwise ENERGY terms in
281
+ ``fort.99`` and corresponding volumes from ``fort.74``.
282
+
283
+ Works on
284
+ --------
285
+ Fort99Handler + Fort74Handler — ``fort.99`` + ``fort.74``
286
+
287
+ Parameters
288
+ ----------
289
+ fort99_handler : TemplateHandler
290
+ Parsed ``fort.99`` handler.
291
+ fort74_handler : TemplateHandler
292
+ Parsed ``fort.74`` handler.
293
+ iden : str
294
+ Identifier (``iden1``) for which the EOS fit is performed.
295
+ source : {"ffield", "qm"}, default="ffield"
296
+ Energy source used for fitting.
297
+ shift_min_to_zero : bool, default=True
298
+ Shift minimum energy to zero to improve numerical conditioning.
299
+ flip_sign : bool, default=False
300
+ Flip the sign of energies before fitting.
301
+ dropna : bool, default=True
302
+ Drop rows with NaN energy or volume values.
303
+
304
+ Returns
305
+ -------
306
+ dict
307
+ Dictionary containing:
308
+ ``iden``, ``source``, ``n_points``, ``V0_A3``, ``K0_eV_A3``,
309
+ ``K0_GPa``, ``E0_eV``, ``C``, ``success``.
310
+
311
+ Examples
312
+ --------
313
+ >>> res = get_fort99_bulk_modulus(
314
+ ... f99, f74, iden="bulk_0", source="ffield"
315
+ ... )
316
+ >>> res["K0_GPa"]
317
+ """
318
+ from scipy.optimize import curve_fit # local import to avoid overload
319
+
320
+ df = fort99_energy_vs_volume(
321
+ fort99_handler=fort99_handler,
322
+ fort74_handler=fort74_handler,
323
+ )
324
+
325
+ if df.empty:
326
+ raise ValueError("No ENERGY vs volume data found (fort99_energy_vs_volume returned empty).")
327
+
328
+ g = df[df["iden1"] == iden].copy()
329
+ if g.empty:
330
+ raise ValueError(f"No rows found for iden1 == {iden!r}.")
331
+
332
+ # Choose energy column
333
+ src = (source or "").strip().lower()
334
+ if src in {"ffield", "ff", "forcefield", "force-field"}:
335
+ e_col = "ffield_value"
336
+ src_name = "ffield"
337
+ elif src in {"qm", "dft", "reference"}:
338
+ e_col = "qm_value"
339
+ src_name = "qm"
340
+ else:
341
+ raise ValueError("source must be one of {'ffield','qm'}.")
342
+
343
+ # Pull V and E
344
+ V = g["V_iden2"].to_numpy(dtype=float)
345
+ E_kcal = g[e_col].to_numpy(dtype=float)
346
+
347
+ if dropna:
348
+ m = np.isfinite(V) & np.isfinite(E_kcal)
349
+ V = V[m]
350
+ E_kcal = E_kcal[m]
351
+
352
+ if len(V) < 6:
353
+ raise ValueError(
354
+ f"Need at least ~6 E(V) points for a stable EOS fit; got {len(V)} for iden={iden!r}."
355
+ )
356
+
357
+ # Sort by V for stability
358
+ order = np.argsort(V)
359
+ V = V[order]
360
+ E_kcal = E_kcal[order]
361
+
362
+ # Optional sign flip (kept for parity with plotting)
363
+ if flip_sign:
364
+ E_kcal = -E_kcal
365
+
366
+ # Convert kcal/mol -> eV
367
+ E = E_kcal * const("energy_kcalmol_to_eV")
368
+
369
+ # Optional energy shift (doesn't affect K0)
370
+ if shift_min_to_zero:
371
+ E = E - np.nanmin(E)
372
+
373
+ # Initial guesses
374
+ V0_guess = float(V[np.nanargmin(E)])
375
+ E0_guess = float(np.nanmin(E))
376
+ K0_guess = 0.5 / const("eV_per_A3_to_GPa")
377
+ C_guess = 4.0
378
+
379
+ p0 = [E0_guess, K0_guess, V0_guess, C_guess]
380
+
381
+ # Basic bounds to prevent nonsense fits
382
+ # K0 > 0, V0 > 0, C > 0
383
+ bounds = (
384
+ [-np.inf, 1e-12, 1e-9, 1e-6],
385
+ [ np.inf, 1e3, np.inf, 1e3],
386
+ )
387
+
388
+ popt, pcov = curve_fit(
389
+ vinet_energy_ev,
390
+ V,
391
+ E,
392
+ p0=p0,
393
+ bounds=bounds,
394
+ maxfev=20000,
395
+ )
396
+
397
+ E0_fit, K0_fit, V0_fit, C_fit = popt
398
+ K0_GPa = float(K0_fit * const("eV_per_A3_to_GPa"))
399
+
400
+ return {
401
+ "iden": iden,
402
+ "source": src_name,
403
+ "n_points": int(len(V)),
404
+ "V0_A3": float(V0_fit),
405
+ "K0_eV_A3": float(K0_fit),
406
+ "K0_GPa": K0_GPa,
407
+ "E0_eV": float(E0_fit),
408
+ "C": float(C_fit),
409
+ "success": True,
410
+ }
411
+