matterviz 0.1.0

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 (131) hide show
  1. package/dist/BohrAtom.svelte +105 -0
  2. package/dist/BohrAtom.svelte.d.ts +21 -0
  3. package/dist/ControlPanel.svelte +158 -0
  4. package/dist/ControlPanel.svelte.d.ts +18 -0
  5. package/dist/Icon.svelte +23 -0
  6. package/dist/Icon.svelte.d.ts +8 -0
  7. package/dist/InfoCard.svelte +79 -0
  8. package/dist/InfoCard.svelte.d.ts +23 -0
  9. package/dist/Nucleus.svelte +64 -0
  10. package/dist/Nucleus.svelte.d.ts +16 -0
  11. package/dist/Spinner.svelte +44 -0
  12. package/dist/Spinner.svelte.d.ts +7 -0
  13. package/dist/api.d.ts +6 -0
  14. package/dist/api.js +30 -0
  15. package/dist/colors/alloy-colors.json +111 -0
  16. package/dist/colors/dark-mode-colors.json +111 -0
  17. package/dist/colors/index.d.ts +26 -0
  18. package/dist/colors/index.js +72 -0
  19. package/dist/colors/jmol-colors.json +111 -0
  20. package/dist/colors/muted-colors.json +111 -0
  21. package/dist/colors/pastel-colors.json +111 -0
  22. package/dist/colors/vesta-colors.json +111 -0
  23. package/dist/composition/BarChart.svelte +260 -0
  24. package/dist/composition/BarChart.svelte.d.ts +33 -0
  25. package/dist/composition/BubbleChart.svelte +166 -0
  26. package/dist/composition/BubbleChart.svelte.d.ts +30 -0
  27. package/dist/composition/Composition.svelte +73 -0
  28. package/dist/composition/Composition.svelte.d.ts +27 -0
  29. package/dist/composition/PieChart.svelte +236 -0
  30. package/dist/composition/PieChart.svelte.d.ts +36 -0
  31. package/dist/composition/index.d.ts +5 -0
  32. package/dist/composition/index.js +5 -0
  33. package/dist/composition/parse.d.ts +14 -0
  34. package/dist/composition/parse.js +307 -0
  35. package/dist/element/ElementHeading.svelte +21 -0
  36. package/dist/element/ElementHeading.svelte.d.ts +8 -0
  37. package/dist/element/ElementPhoto.svelte +56 -0
  38. package/dist/element/ElementPhoto.svelte.d.ts +9 -0
  39. package/dist/element/ElementStats.svelte +73 -0
  40. package/dist/element/ElementStats.svelte.d.ts +8 -0
  41. package/dist/element/ElementTile.svelte +449 -0
  42. package/dist/element/ElementTile.svelte.d.ts +25 -0
  43. package/dist/element/data.d.ts +4958 -0
  44. package/dist/element/data.js +5628 -0
  45. package/dist/element/index.d.ts +4 -0
  46. package/dist/element/index.js +4 -0
  47. package/dist/icons.d.ts +435 -0
  48. package/dist/icons.js +435 -0
  49. package/dist/index.d.ts +82 -0
  50. package/dist/index.js +43 -0
  51. package/dist/io/decompress.d.ts +16 -0
  52. package/dist/io/decompress.js +78 -0
  53. package/dist/io/export.d.ts +9 -0
  54. package/dist/io/export.js +205 -0
  55. package/dist/io/parse.d.ts +53 -0
  56. package/dist/io/parse.js +747 -0
  57. package/dist/labels.d.ts +31 -0
  58. package/dist/labels.js +209 -0
  59. package/dist/material/MaterialCard.svelte +135 -0
  60. package/dist/material/MaterialCard.svelte.d.ts +10 -0
  61. package/dist/material/SymmetryCard.svelte +23 -0
  62. package/dist/material/SymmetryCard.svelte.d.ts +9 -0
  63. package/dist/material/index.d.ts +2 -0
  64. package/dist/material/index.js +2 -0
  65. package/dist/math.d.ts +24 -0
  66. package/dist/math.js +216 -0
  67. package/dist/periodic-table/PeriodicTable.svelte +284 -0
  68. package/dist/periodic-table/PeriodicTable.svelte.d.ts +50 -0
  69. package/dist/periodic-table/PropertySelect.svelte +20 -0
  70. package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
  71. package/dist/periodic-table/TableInset.svelte +18 -0
  72. package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
  73. package/dist/periodic-table/index.d.ts +9 -0
  74. package/dist/periodic-table/index.js +3 -0
  75. package/dist/plot/ColorBar.svelte +414 -0
  76. package/dist/plot/ColorBar.svelte.d.ts +22 -0
  77. package/dist/plot/ColorScaleSelect.svelte +31 -0
  78. package/dist/plot/ColorScaleSelect.svelte.d.ts +15 -0
  79. package/dist/plot/ElementScatter.svelte +38 -0
  80. package/dist/plot/ElementScatter.svelte.d.ts +14 -0
  81. package/dist/plot/Line.svelte +42 -0
  82. package/dist/plot/Line.svelte.d.ts +15 -0
  83. package/dist/plot/PlotLegend.svelte +206 -0
  84. package/dist/plot/PlotLegend.svelte.d.ts +18 -0
  85. package/dist/plot/ScatterPlot.svelte +1753 -0
  86. package/dist/plot/ScatterPlot.svelte.d.ts +114 -0
  87. package/dist/plot/ScatterPlotControls.svelte +505 -0
  88. package/dist/plot/ScatterPlotControls.svelte.d.ts +33 -0
  89. package/dist/plot/ScatterPoint.svelte +72 -0
  90. package/dist/plot/ScatterPoint.svelte.d.ts +17 -0
  91. package/dist/plot/index.d.ts +168 -0
  92. package/dist/plot/index.js +46 -0
  93. package/dist/state.svelte.d.ts +12 -0
  94. package/dist/state.svelte.js +11 -0
  95. package/dist/structure/Bond.svelte +68 -0
  96. package/dist/structure/Bond.svelte.d.ts +13 -0
  97. package/dist/structure/Lattice.svelte +115 -0
  98. package/dist/structure/Lattice.svelte.d.ts +15 -0
  99. package/dist/structure/Structure.svelte +298 -0
  100. package/dist/structure/Structure.svelte.d.ts +28 -0
  101. package/dist/structure/StructureCard.svelte +26 -0
  102. package/dist/structure/StructureCard.svelte.d.ts +9 -0
  103. package/dist/structure/StructureControls.svelte +383 -0
  104. package/dist/structure/StructureControls.svelte.d.ts +23 -0
  105. package/dist/structure/StructureLegend.svelte +130 -0
  106. package/dist/structure/StructureLegend.svelte.d.ts +17 -0
  107. package/dist/structure/StructureScene.svelte +331 -0
  108. package/dist/structure/StructureScene.svelte.d.ts +47 -0
  109. package/dist/structure/bonding.d.ts +16 -0
  110. package/dist/structure/bonding.js +150 -0
  111. package/dist/structure/index.d.ts +98 -0
  112. package/dist/structure/index.js +114 -0
  113. package/dist/structure/pbc.d.ts +6 -0
  114. package/dist/structure/pbc.js +72 -0
  115. package/dist/trajectory/Sidebar.svelte +412 -0
  116. package/dist/trajectory/Sidebar.svelte.d.ts +14 -0
  117. package/dist/trajectory/Trajectory.svelte +1084 -0
  118. package/dist/trajectory/Trajectory.svelte.d.ts +49 -0
  119. package/dist/trajectory/TrajectoryError.svelte +120 -0
  120. package/dist/trajectory/TrajectoryError.svelte.d.ts +12 -0
  121. package/dist/trajectory/extract.d.ts +5 -0
  122. package/dist/trajectory/extract.js +157 -0
  123. package/dist/trajectory/index.d.ts +16 -0
  124. package/dist/trajectory/index.js +49 -0
  125. package/dist/trajectory/parse.d.ts +13 -0
  126. package/dist/trajectory/parse.js +1093 -0
  127. package/dist/trajectory/plotting.d.ts +12 -0
  128. package/dist/trajectory/plotting.js +148 -0
  129. package/license +21 -0
  130. package/package.json +131 -0
  131. package/readme.md +95 -0
@@ -0,0 +1,31 @@
1
+ import type { Category, ChemicalElement } from './';
2
+ export declare const property_labels: Partial<Record<keyof ChemicalElement, [string, string | null]>>;
3
+ export declare const heatmap_keys: (keyof ChemicalElement)[];
4
+ export declare const heatmap_labels: Partial<Record<string, keyof ChemicalElement>>;
5
+ export declare const default_fmt: [string, string];
6
+ export declare const format_num: (num: number, fmt?: string | number) => string;
7
+ export declare function parse_si_float<T extends string | number | null | undefined>(value: T): T | number | string;
8
+ export declare const category_counts: Record<Category, number>;
9
+ export declare const categories: readonly ["actinide", "alkali metal", "alkaline earth metal", "diatomic nonmetal", "lanthanide", "metalloid", "noble gas", "polyatomic nonmetal", "post-transition metal", "transition metal"];
10
+ export declare const elem_symbols: readonly ["H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og"];
11
+ export declare function luminance(clr: string): number;
12
+ export declare function get_bg_color(elem: HTMLElement | null, bg_color?: string | null): string;
13
+ export declare function choose_bw_for_contrast(node: HTMLElement | null, bg_color?: string | null, text_color_threshold?: number): "white" | "black";
14
+ export declare const superscript_map: {
15
+ readonly '0': "⁰";
16
+ readonly '1': "¹";
17
+ readonly '2': "²";
18
+ readonly '3': "³";
19
+ readonly '4': "⁴";
20
+ readonly '5': "⁵";
21
+ readonly '6': "⁶";
22
+ readonly '7': "⁷";
23
+ readonly '8': "⁸";
24
+ readonly '9': "⁹";
25
+ readonly '+': "⁺";
26
+ readonly '-': "⁻";
27
+ };
28
+ export declare function superscript_digits(input: string): string;
29
+ export declare const trajectory_labels: Record<string, string>;
30
+ export declare function get_clean_label(key: string, property_labels?: Record<string, string>): string;
31
+ export declare function get_label_with_unit(key: string, property_labels?: Record<string, string>, units?: Record<string, string>): string;
package/dist/labels.js ADDED
@@ -0,0 +1,209 @@
1
+ import { rgb } from 'd3-color';
2
+ import { format } from 'd3-format';
3
+ // TODO add labels and units for all elemental properties
4
+ export const property_labels = {
5
+ atomic_mass: [`Atomic Mass`, `u`],
6
+ atomic_radius: [`Atomic Radius`, `Å`],
7
+ boiling_point: [`Boiling Point`, `K`],
8
+ covalent_radius: [`Covalent Radius`, `Å`],
9
+ density: [`Density`, `g/cm³`],
10
+ electron_affinity: [`Electron Affinity`, null],
11
+ electronegativity: [`Electronegativity`, null],
12
+ first_ionization: [`First Ionization Energy`, `eV`],
13
+ melting_point: [`Melting Point`, `K`],
14
+ // molar_heat: [`Molar Heat`, `J/(mol·K)`],
15
+ n_shells: [`Number of Shells`, null],
16
+ n_valence: [`Electron Valency`, null],
17
+ number: [`Atomic Number`, null],
18
+ shells: [`Electron Shell Occupations`, null],
19
+ specific_heat: [`Specific Heat`, `J/(g K)`],
20
+ };
21
+ export const heatmap_keys = [
22
+ `atomic_mass`,
23
+ `atomic_radius`,
24
+ `covalent_radius`,
25
+ `electronegativity`,
26
+ `density`,
27
+ `boiling_point`,
28
+ `melting_point`,
29
+ `first_ionization`,
30
+ ];
31
+ export const heatmap_labels = Object
32
+ .fromEntries(heatmap_keys.map((key) => {
33
+ const [label, unit] = property_labels[key] ?? [];
34
+ if (!label)
35
+ throw `Unexpected missing label ${label}`;
36
+ return [label + (unit ? ` (${unit})` : ``), key];
37
+ }));
38
+ // allow users to import default_fmt and change it's items in place to
39
+ // set default number format globally
40
+ export const default_fmt = [`,.3~s`, `.3~g`];
41
+ // fmt as number only allowed to support [].map(format_num) without type error
42
+ export const format_num = (num, fmt) => {
43
+ if (num === null)
44
+ return ``;
45
+ if (!fmt || typeof fmt !== `string`) {
46
+ const [gt_1_fmt, lt_1_fmt] = default_fmt;
47
+ return format(Math.abs(num) >= 1 ? gt_1_fmt : lt_1_fmt)(num);
48
+ }
49
+ return format(fmt)(num);
50
+ };
51
+ export function parse_si_float(value) {
52
+ // if not string, return as is
53
+ if (typeof value !== `string`)
54
+ return value;
55
+ // Remove whitespace and commas
56
+ const cleaned = value.trim().replace(/(\d),(\d)/g, `$1$2`);
57
+ // Check if the value is a SI-formatted number (e.g., "1.23k", "4.56M", "789µ", "12n")
58
+ const match = cleaned.match(/^([-+]?\d*\.?\d+)\s*([yzafpnµmkMGTPEZY])?$/i);
59
+ if (match) {
60
+ const [, num_part, suffix] = match;
61
+ let multiplier = 1;
62
+ if (suffix) {
63
+ const suffixes = `yzafpnµm kMGTPEZY`;
64
+ const index = suffixes.indexOf(suffix);
65
+ if (index !== -1) {
66
+ multiplier = Math.pow(1000, index - 8);
67
+ }
68
+ }
69
+ return parseFloat(num_part) * multiplier;
70
+ }
71
+ // If it's a number without SI suffix, try parsing it
72
+ if (/^[-+]?[\d,]+\.?\d*$/.test(cleaned))
73
+ return parseFloat(cleaned);
74
+ // If the value is not a formatted number, return as is
75
+ return value;
76
+ }
77
+ export const category_counts = {
78
+ actinide: 15,
79
+ 'alkali metal': 6,
80
+ 'alkaline earth metal': 6,
81
+ 'diatomic nonmetal': 7,
82
+ lanthanide: 15,
83
+ metalloid: 8,
84
+ 'noble gas': 7,
85
+ 'polyatomic nonmetal': 4,
86
+ 'post-transition metal': 12,
87
+ 'transition metal': 38,
88
+ };
89
+ export const categories = [
90
+ `actinide`,
91
+ `alkali metal`,
92
+ `alkaline earth metal`,
93
+ `diatomic nonmetal`,
94
+ `lanthanide`,
95
+ `metalloid`,
96
+ `noble gas`,
97
+ `polyatomic nonmetal`,
98
+ `post-transition metal`,
99
+ `transition metal`,
100
+ ];
101
+ // deno-fmt-ignore-next-line
102
+ export const elem_symbols = [`H`, `He`, `Li`, `Be`, `B`, `C`, `N`, `O`, `F`, `Ne`, `Na`, `Mg`, `Al`, `Si`, `P`, `S`, `Cl`, `Ar`, `K`, `Ca`, `Sc`, `Ti`, `V`, `Cr`, `Mn`, `Fe`, `Co`, `Ni`, `Cu`, `Zn`, `Ga`, `Ge`, `As`, `Se`, `Br`, `Kr`, `Rb`, `Sr`, `Y`, `Zr`, `Nb`, `Mo`, `Tc`, `Ru`, `Rh`, `Pd`, `Ag`, `Cd`, `In`, `Sn`, `Sb`, `Te`, `I`, `Xe`, `Cs`, `Ba`, `La`, `Ce`, `Pr`, `Nd`, `Pm`, `Sm`, `Eu`, `Gd`, `Tb`, `Dy`, `Ho`, `Er`, `Tm`, `Yb`, `Lu`, `Hf`, `Ta`, `W`, `Re`, `Os`, `Ir`, `Pt`, `Au`, `Hg`, `Tl`, `Pb`, `Bi`, `Po`, `At`, `Rn`, `Fr`, `Ra`, `Ac`, `Th`, `Pa`, `U`, `Np`, `Pu`, `Am`, `Cm`, `Bk`, `Cf`, `Es`, `Fm`, `Md`, `No`, `Lr`, `Rf`, `Db`, `Sg`, `Bh`, `Hs`, `Mt`, `Ds`, `Rg`, `Cn`, `Nh`, `Fl`, `Mc`, `Lv`, `Ts`, `Og`];
103
+ // calculate human-perceived brightness from RGB color
104
+ export function luminance(clr) {
105
+ const { r, g, b } = rgb(clr);
106
+ // if (![r, g, b].every((c) => c >= 0 && c <= 255)) {
107
+ // console.error(`invalid RGB color: ${clr}, parsed to rgb=${r},${g},${b}`)
108
+ // }
109
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255; // https://stackoverflow.com/a/596243
110
+ }
111
+ // get background color of passed DOM node, or recurse up the DOM tree if current node is transparent
112
+ export function get_bg_color(elem, bg_color = null) {
113
+ if (bg_color)
114
+ return bg_color;
115
+ // recurse up the DOM tree to find the first non-transparent background color
116
+ const transparent = `rgba(0, 0, 0, 0)`;
117
+ if (!elem)
118
+ return transparent; // if no DOM node, return transparent
119
+ const bg = getComputedStyle(elem).backgroundColor; // get node background color
120
+ if (bg !== transparent)
121
+ return bg; // if not transparent, return it
122
+ return get_bg_color(elem.parentElement); // otherwise recurse up the DOM tree
123
+ }
124
+ // pick black or white text color to maximize contrast with background
125
+ export function choose_bw_for_contrast(node,
126
+ // you can explicitly pass bg_color to avoid DOM recursion and in case get_bg_color() fails
127
+ bg_color = null, text_color_threshold = 0.7) {
128
+ const light_bg = luminance(get_bg_color(node, bg_color)) > text_color_threshold;
129
+ return light_bg ? `black` : `white`; // white text for dark backgrounds, black for light
130
+ }
131
+ export const superscript_map = {
132
+ '0': `⁰`,
133
+ '1': `¹`,
134
+ '2': `²`,
135
+ '3': `³`,
136
+ '4': `⁴`,
137
+ '5': `⁵`,
138
+ '6': `⁶`,
139
+ '7': `⁷`,
140
+ '8': `⁸`,
141
+ '9': `⁹`,
142
+ '+': `⁺`,
143
+ '-': `⁻`,
144
+ };
145
+ export function superscript_digits(input) {
146
+ // use replace all signs and digits with their unicode superscript equivalent
147
+ return input.replace(/[\d+-]/g, (match) => superscript_map[match] ?? match);
148
+ }
149
+ // Trajectory property labels: controls how properties are displayed in trajectory plots
150
+ export const trajectory_labels = {
151
+ // Energy properties
152
+ energy: `Energy (eV)`,
153
+ energy_per_atom: `Energy per atom (eV/atom)`,
154
+ potential_energy: `Potential energy (eV)`,
155
+ kinetic_energy: `Kinetic energy (eV)`,
156
+ total_energy: `Total energy (eV)`,
157
+ // Force properties
158
+ force_max: `F<sub>max</sub> (eV/Å)`,
159
+ force_norm: `F<sub>norm</sub> (eV/Å)`,
160
+ 'Force Max': `Force Max (eV/Å)`,
161
+ 'Force RMS': `Force RMS (eV/Å)`,
162
+ // Structural properties
163
+ volume: `Volume (ų)`,
164
+ Volume: `Cell Volume (ų)`,
165
+ density: `Density (g/cm³)`,
166
+ // Thermodynamic properties
167
+ temperature: `Temperature (K)`,
168
+ pressure: `Pressure (GPa)`,
169
+ stress_max: `σ<sub>max</sub> (GPa)`,
170
+ stress_frobenius: `σ<sub>F</sub> (GPa)`,
171
+ };
172
+ // Helper function to get clean label without units
173
+ export function get_clean_label(key, property_labels) {
174
+ // First check if we have an explicit label mapping
175
+ if (property_labels?.[key]) {
176
+ // Remove any existing unit notation from the label
177
+ return property_labels[key].replace(/\s*\([^)]*\)\s*$/, ``);
178
+ }
179
+ const lower_key = key.toLowerCase();
180
+ // Special formatting for force properties
181
+ if (lower_key === `force_max` || key === `Force Max`) {
182
+ return `F<sub>max</sub>`;
183
+ }
184
+ if (lower_key === `force_norm` || key === `Force RMS`) {
185
+ return `F<sub>norm</sub>`;
186
+ }
187
+ if (lower_key === `stress_max`) {
188
+ return `σ<sub>max</sub>`;
189
+ }
190
+ if (lower_key === `stress_frobenius`) {
191
+ return `σ<sub>F</sub>`;
192
+ }
193
+ if (lower_key === `temperature`) {
194
+ return `Temperature`;
195
+ }
196
+ // Capitalize the key name for all other properties
197
+ return key.charAt(0).toUpperCase() + key.slice(1);
198
+ }
199
+ // Helper function to get property label with unit for trajectory plotting
200
+ export function get_label_with_unit(key, property_labels, units) {
201
+ // First check if we have an explicit label mapping
202
+ if (property_labels?.[key])
203
+ return property_labels[key];
204
+ // Get clean label and add unit if available
205
+ const clean_label = get_clean_label(key, property_labels);
206
+ const lower_key = key.toLowerCase();
207
+ const unit = units?.[lower_key] || units?.[key] || ``;
208
+ return unit ? `${clean_label} (${unit})` : clean_label;
209
+ }
@@ -0,0 +1,135 @@
1
+ <script lang="ts">import { format_num, InfoCard, superscript_digits } from '..';
2
+ import SymmetryCard from './SymmetryCard.svelte';
3
+ let { material, after_symmetry, ...rest } = $props();
4
+ let data = $derived([
5
+ {
6
+ title: `Band Gap`,
7
+ value: material.band_gap,
8
+ unit: `eV`,
9
+ tooltip: material.vbm && material.cbm
10
+ ? `VBM: ${material.vbm}, CBM: ${material.cbm}`
11
+ : ``,
12
+ },
13
+ {
14
+ title: `Space Group`,
15
+ value: `${material.symmetry?.number}`,
16
+ unit: `(${material.symmetry?.symbol})`,
17
+ condition: material.symmetry?.number,
18
+ },
19
+ {
20
+ title: `E<sub>above hull</sub>`,
21
+ value: 1000 * (material?.energy_above_hull ?? 0),
22
+ unit: `meV/atom`,
23
+ condition: `energy_above_hull` in material,
24
+ },
25
+ {
26
+ title: `Predicted stable`,
27
+ value: (material?.energy_above_hull ?? 0 > 0) ? `❌ No` : `✅ Yes`,
28
+ condition: `energy_above_hull` in material,
29
+ },
30
+ {
31
+ title: `Formation Energy`,
32
+ value: material.formation_energy_per_atom,
33
+ unit: `eV/atom`,
34
+ },
35
+ {
36
+ title: `Experimentally Observed`,
37
+ value: material.theoretical ? `❌ No` : `✅ Yes`,
38
+ },
39
+ { title: `Total Energy`, value: material.energy_per_atom, unit: `eV/atom` },
40
+ {
41
+ title: `Uncorrected Energy`,
42
+ value: material.uncorrected_energy_per_atom,
43
+ unit: `eV/atom`,
44
+ condition: material.uncorrected_energy_per_atom != material.energy_per_atom,
45
+ },
46
+ {
47
+ title: `Last updated`,
48
+ value: material.last_updated?.$date.split(`T`)[0],
49
+ },
50
+ {
51
+ title: `Origins`,
52
+ value: material.origins,
53
+ condition: material.origins?.length,
54
+ },
55
+ {
56
+ title: `Voigt bulk modulus`,
57
+ value: material.k_voigt,
58
+ unit: `GPa`,
59
+ },
60
+ {
61
+ title: `Voig shear modulus`,
62
+ value: material.g_voigt,
63
+ unit: `GPa`,
64
+ },
65
+ { title: `Refractive index`, value: material.n },
66
+ {
67
+ title: `Is magnetic`,
68
+ value: `${material.is_magnetic ? `yes` : `no`} ${material.is_magnetic
69
+ ? `(${format_num(material.total_magnetization)} µB/f.u.)`
70
+ : ``}`,
71
+ tooltip: `µB: Bohr magneton, f.u.: formula unit`,
72
+ },
73
+ {
74
+ title: `Ordering`,
75
+ value: {
76
+ NM: `non-magnetic`,
77
+ FM: `ferromagnetic`,
78
+ FiM: `ferrimagnetic`,
79
+ AFM: `antiferromagnetic`,
80
+ }[material.ordering] ?? `unknown`,
81
+ },
82
+ {
83
+ title: `Possible oxidation states`,
84
+ value: superscript_digits(material.possible_species?.join(` `) ?? ``),
85
+ condition: material.possible_species?.length,
86
+ },
87
+ ]);
88
+ </script>
89
+
90
+ <InfoCard {data} {...rest} title="Material" />
91
+
92
+ <SymmetryCard {material} />
93
+
94
+ {@render after_symmetry?.()}
95
+
96
+ <details>
97
+ <summary>Related material IDs</summary>
98
+ {#if material.task_ids?.length}
99
+ <p>
100
+ Task IDs: {#each material.task_ids as id (id)}
101
+ <a href="https://materialsproject.org/tasks/{id}">{id}</a>
102
+ {/each}
103
+ </p>
104
+ {/if}
105
+ {#if material.database_IDs?.icsd?.length}
106
+ <p>
107
+ ICSD IDs: {#each material.database_IDs.icsd as id (id)}
108
+ {@const href =
109
+ `https://ccdc.cam.ac.uk/structures/Search?Ccdcid=${id}&DatabaseToSearch=ICSD`}
110
+ <a {href}>{id}</a>
111
+ {/each}
112
+ </p>
113
+ {/if}
114
+ </details>
115
+
116
+ <p class="warning">
117
+ {material.warnings}
118
+ </p>
119
+
120
+ <style>
121
+ .warning {
122
+ color: var(--warning-color, darkred);
123
+ }
124
+ p {
125
+ display: flex;
126
+ flex-wrap: wrap;
127
+ gap: 1ex 1em;
128
+ font-size: smaller;
129
+ }
130
+ p a {
131
+ background-color: rgba(255, 255, 255, 0.1);
132
+ padding: 0 3pt;
133
+ border-radius: 3pt;
134
+ }
135
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { SummaryDoc } from '../../types';
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ material: SummaryDoc;
5
+ after_symmetry?: Snippet;
6
+ [key: string]: unknown;
7
+ }
8
+ declare const MaterialCard: import("svelte").Component<Props, {}, "">;
9
+ type MaterialCard = ReturnType<typeof MaterialCard>;
10
+ export default MaterialCard;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">import { InfoCard } from '..';
2
+ let { title = `Symmetry`, material, ...rest } = $props();
3
+ let data = $derived([
4
+ {
5
+ title: `Crystal System`,
6
+ value: `${material.symmetry?.crystal_system}`,
7
+ },
8
+ {
9
+ title: `International Number`,
10
+ value: material.symmetry?.number,
11
+ },
12
+ {
13
+ title: `Point Group`,
14
+ value: `${material.symmetry?.point_group}`,
15
+ },
16
+ {
17
+ title: `Symbol`,
18
+ value: `${material.symmetry?.symbol}`,
19
+ },
20
+ ]);
21
+ </script>
22
+
23
+ <InfoCard {data} {...rest} {title} />
@@ -0,0 +1,9 @@
1
+ import type { SummaryDoc } from '../../types';
2
+ interface Props {
3
+ title?: string;
4
+ material: SummaryDoc;
5
+ [key: string]: unknown;
6
+ }
7
+ declare const SymmetryCard: import("svelte").Component<Props, {}, "">;
8
+ type SymmetryCard = ReturnType<typeof SymmetryCard>;
9
+ export default SymmetryCard;
@@ -0,0 +1,2 @@
1
+ export { default as MaterialCard } from './MaterialCard.svelte';
2
+ export { default as SymmetryCard } from './SymmetryCard.svelte';
@@ -0,0 +1,2 @@
1
+ export { default as MaterialCard } from './MaterialCard.svelte';
2
+ export { default as SymmetryCard } from './SymmetryCard.svelte';
package/dist/math.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { LatticeParams } from './structure/index';
2
+ export type Vec3 = [number, number, number];
3
+ export type Matrix3x3 = [Vec3, Vec3, Vec3];
4
+ export type NdVector = number[];
5
+ export declare function calc_lattice_params(matrix: Matrix3x3): LatticeParams & {
6
+ volume: number;
7
+ };
8
+ export declare function norm(vec: NdVector): number;
9
+ export declare function scale(vec: NdVector, factor: number): NdVector;
10
+ export declare function euclidean_dist(vec1: Vec3, vec2: Vec3): number;
11
+ export declare function pbc_dist(pos1: Vec3, // First position vector (Cartesian coordinates)
12
+ pos2: Vec3, // Second position vector (Cartesian coordinates)
13
+ lattice_matrix: Matrix3x3, // 3x3 lattice matrix where each row is a lattice vector
14
+ lattice_inv?: Matrix3x3): number;
15
+ export declare function matrix_inverse_3x3(matrix: Matrix3x3): Matrix3x3;
16
+ export declare function mat3x3_vec3_multiply(matrix: Matrix3x3, vector: Vec3): Vec3;
17
+ export declare function add(...vecs: NdVector[]): NdVector;
18
+ export declare function dot(vec1: NdVector, vec2: NdVector): number | number[] | number[][];
19
+ export declare function to_voigt(tensor: number[][]): number[];
20
+ export declare function from_voigt(voigt: number[]): number[][];
21
+ export declare function vec9_to_mat3x3(flat_array: number[]): number[][];
22
+ export declare function tensor_to_flat_array(tensor: number[][]): number[];
23
+ export declare const transpose_matrix: (matrix: Matrix3x3) => Matrix3x3;
24
+ export declare function cell_to_lattice_matrix(a: number, b: number, c: number, alpha: number, beta: number, gamma: number): Matrix3x3;
package/dist/math.js ADDED
@@ -0,0 +1,216 @@
1
+ // Calculate all lattice parameters in a single efficient pass
2
+ export function calc_lattice_params(matrix) {
3
+ const [a_vec, b_vec, c_vec] = matrix;
4
+ // Calculate vector lengths (lattice parameters a, b, c)
5
+ const a = Math.sqrt(a_vec[0] ** 2 + a_vec[1] ** 2 + a_vec[2] ** 2);
6
+ const b = Math.sqrt(b_vec[0] ** 2 + b_vec[1] ** 2 + b_vec[2] ** 2);
7
+ const c = Math.sqrt(c_vec[0] ** 2 + c_vec[1] ** 2 + c_vec[2] ** 2);
8
+ // Calculate volume using scalar triple product
9
+ const volume = Math.abs(a_vec[0] * (b_vec[1] * c_vec[2] - b_vec[2] * c_vec[1]) +
10
+ a_vec[1] * (b_vec[2] * c_vec[0] - b_vec[0] * c_vec[2]) +
11
+ a_vec[2] * (b_vec[0] * c_vec[1] - b_vec[1] * c_vec[0]));
12
+ // Calculate dot products for angles (only once each)
13
+ const dot_ab = a_vec[0] * b_vec[0] + a_vec[1] * b_vec[1] + a_vec[2] * b_vec[2];
14
+ const dot_ac = a_vec[0] * c_vec[0] + a_vec[1] * c_vec[1] + a_vec[2] * c_vec[2];
15
+ const dot_bc = b_vec[0] * c_vec[0] + b_vec[1] * c_vec[1] + b_vec[2] * c_vec[2];
16
+ // Convert to angles in degrees
17
+ const rad_to_deg = 180 / Math.PI;
18
+ const alpha = Math.acos(dot_bc / (b * c)) * rad_to_deg;
19
+ const beta = Math.acos(dot_ac / (a * c)) * rad_to_deg;
20
+ const gamma = Math.acos(dot_ab / (a * b)) * rad_to_deg;
21
+ return { a, b, c, alpha, beta, gamma, volume };
22
+ }
23
+ export function norm(vec) {
24
+ return Math.sqrt(vec.reduce((acc, val) => acc + val ** 2, 0));
25
+ }
26
+ export function scale(vec, factor) {
27
+ return vec.map((component) => component * factor);
28
+ }
29
+ export function euclidean_dist(vec1, vec2) {
30
+ return norm(add(vec1, scale(vec2, -1)));
31
+ }
32
+ // Calculate the minimum distance between two points considering periodic boundary conditions.
33
+ export function pbc_dist(pos1, // First position vector (Cartesian coordinates)
34
+ pos2, // Second position vector (Cartesian coordinates)
35
+ lattice_matrix, // 3x3 lattice matrix where each row is a lattice vector
36
+ lattice_inv) {
37
+ // Use provided inverse or compute it
38
+ const inv_matrix = lattice_inv ?? matrix_inverse_3x3(lattice_matrix);
39
+ // Convert Cartesian coordinates to fractional coordinates
40
+ const frac1 = mat3x3_vec3_multiply(inv_matrix, pos1);
41
+ const frac2 = mat3x3_vec3_multiply(inv_matrix, pos2);
42
+ // Calculate fractional distance vector
43
+ const frac_diff = add(frac1, scale(frac2, -1));
44
+ // Apply minimum image convention: wrap to [-0.5, 0.5)
45
+ const wrapped_frac_diff = frac_diff.map((x) => {
46
+ // Wrap to [0, 1) first, then shift to [-0.5, 0.5)
47
+ let wrapped = x - Math.floor(x);
48
+ if (wrapped >= 0.5)
49
+ wrapped -= 1;
50
+ return wrapped;
51
+ });
52
+ // Convert back to Cartesian coordinates
53
+ const cart_diff = mat3x3_vec3_multiply(lattice_matrix, wrapped_frac_diff);
54
+ return norm(cart_diff);
55
+ }
56
+ export function matrix_inverse_3x3(matrix) {
57
+ /** Calculate the inverse of a 3x3 matrix */
58
+ const [[a, b, c], [d, e, f], [g, h, i]] = matrix;
59
+ const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
60
+ if (Math.abs(det) < 1e-10) {
61
+ throw `Matrix is singular and cannot be inverted`;
62
+ }
63
+ const inv_det = 1 / det;
64
+ return [
65
+ [
66
+ (e * i - f * h) * inv_det,
67
+ (c * h - b * i) * inv_det,
68
+ (b * f - c * e) * inv_det,
69
+ ],
70
+ [
71
+ (f * g - d * i) * inv_det,
72
+ (a * i - c * g) * inv_det,
73
+ (c * d - a * f) * inv_det,
74
+ ],
75
+ [
76
+ (d * h - e * g) * inv_det,
77
+ (b * g - a * h) * inv_det,
78
+ (a * e - b * d) * inv_det,
79
+ ],
80
+ ];
81
+ }
82
+ // Multiply a 3x3 matrix by a 3D vector
83
+ export function mat3x3_vec3_multiply(matrix, vector) {
84
+ const [a, b, c] = matrix;
85
+ const [x, y, z] = vector;
86
+ return [
87
+ a[0] * x + a[1] * y + a[2] * z,
88
+ b[0] * x + b[1] * y + b[2] * z,
89
+ c[0] * x + c[1] * y + c[2] * z,
90
+ ];
91
+ }
92
+ export function add(...vecs) {
93
+ // add up any number of same-length vectors
94
+ if (vecs.length === 0)
95
+ return [];
96
+ const first_vec = vecs[0];
97
+ const length = first_vec.length;
98
+ // Validate all vectors have the same length
99
+ for (const vec of vecs) {
100
+ if (vec.length !== length) {
101
+ throw `All vectors must have the same length`;
102
+ }
103
+ }
104
+ const result = new Array(length).fill(0);
105
+ for (const vec of vecs) {
106
+ for (let idx = 0; idx < length; idx++) {
107
+ result[idx] += vec[idx];
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ export function dot(vec1, vec2) {
113
+ // Handle the case where both inputs are scalars
114
+ if (typeof vec1 === `number` && typeof vec2 === `number`) {
115
+ return vec1 * vec2;
116
+ }
117
+ // Handle the case where one input is a scalar and the other is a vector
118
+ if (typeof vec1 === `number` && Array.isArray(vec2)) {
119
+ throw `Scalar and vector multiplication is not supported`;
120
+ }
121
+ if (Array.isArray(vec1) && typeof vec2 === `number`) {
122
+ throw `vector and scalar multiplication is not supported`;
123
+ }
124
+ // Handle the case where both inputs are vectors
125
+ if (!Array.isArray(vec1[0]) && !Array.isArray(vec2[0])) {
126
+ if (vec1.length !== vec2.length) {
127
+ throw `Vectors must be of same length`;
128
+ }
129
+ return vec1.reduce((sum, val, index) => sum + val * vec2[index], 0);
130
+ }
131
+ // Handle the case where the first input is a matrix and the second is a vector
132
+ if (Array.isArray(vec1[0]) && !Array.isArray(vec2[0])) {
133
+ const mat1 = vec1;
134
+ if (mat1[0].length !== vec2.length) {
135
+ throw `Number of columns in matrix must be equal to number of elements in vector`;
136
+ }
137
+ return mat1.map((row) => row.reduce((sum, val, index) => sum + val * vec2[index], 0));
138
+ }
139
+ // Handle the case where both inputs are matrices
140
+ if (Array.isArray(vec1[0]) && Array.isArray(vec2[0])) {
141
+ const mat1 = vec1;
142
+ const mat2 = vec2;
143
+ if (mat1[0].length !== mat2.length) {
144
+ throw `Number of columns in first matrix must be equal to number of rows in second matrix`;
145
+ }
146
+ return mat1.map((row, i) => mat2[0].map((_, j) => row.reduce((sum, _, k) => sum + mat1[i][k] * mat2[k][j], 0)));
147
+ }
148
+ // Handle any other cases
149
+ throw `Unsupported input dimensions. Inputs must be scalars, vectors, or matrices.`;
150
+ }
151
+ // Conversion utilities for vectors and tensors below
152
+ // Convert 3x3 symmetric tensor to 6-element Voigt notation vector
153
+ // Voigt notation maps: (1,1)->1, (2,2)->2, (3,3)->3, (2,3)->4, (1,3)->5, (1,2)->6
154
+ export function to_voigt(tensor) {
155
+ if (tensor.length !== 3 || !tensor.every((row) => row.length === 3)) {
156
+ throw new Error(`Expected 3x3 tensor, got ${tensor.length}x${tensor[0]?.length ?? `n/a`}`);
157
+ }
158
+ const [t11, t12, t13, _t21, t22, t23, _t31, _t32, t33] = tensor.flat();
159
+ return [t11, t22, t33, t23, t13, t12];
160
+ }
161
+ // Convert 6-element Voigt notation vector to 3x3 symmetric tensor
162
+ export function from_voigt(voigt) {
163
+ if (voigt.length !== 6) {
164
+ throw new Error(`Expected 6-element Voigt vector, got ${voigt.length} elements`);
165
+ }
166
+ const [v1, v2, v3, v4, v5, v6] = voigt;
167
+ return [[v1, v6, v5], [v6, v2, v4], [v5, v4, v3]];
168
+ }
169
+ // Convert flat 9-element array to 3x3 tensor (row-major order)
170
+ export function vec9_to_mat3x3(flat_array) {
171
+ if (flat_array.length !== 9) {
172
+ throw new Error(`Expected 9-element array, got ${flat_array.length} elements`);
173
+ }
174
+ const [a1, a2, a3, a4, a5, a6, a7, a8, a9] = flat_array;
175
+ return [[a1, a2, a3], [a4, a5, a6], [a7, a8, a9]];
176
+ }
177
+ // Convert 3x3 tensor to flat 9-element array (row-major order)
178
+ export function tensor_to_flat_array(tensor) {
179
+ if (tensor.length !== 3 || !tensor.every((row) => row.length === 3)) {
180
+ throw new Error(`Expected 3x3 tensor, got ${tensor.length}x${tensor[0]?.length ?? `n/a`}`);
181
+ }
182
+ const [t11, t12, t13, t21, t22, t23, t31, t32, t33] = tensor.flat();
183
+ return [t11, t12, t13, t21, t22, t23, t31, t32, t33];
184
+ }
185
+ // Transpose a 3x3 matrix
186
+ export const transpose_matrix = (matrix) => [
187
+ [matrix[0][0], matrix[1][0], matrix[2][0]],
188
+ [matrix[0][1], matrix[1][1], matrix[2][1]],
189
+ [matrix[0][2], matrix[1][2], matrix[2][2]],
190
+ ];
191
+ // Convert unit cell parameters to lattice matrix (crystallographic convention)
192
+ export function cell_to_lattice_matrix(a, b, c, alpha, beta, gamma) {
193
+ // Convert angles to radians
194
+ const alpha_rad = (alpha * Math.PI) / 180;
195
+ const beta_rad = (beta * Math.PI) / 180;
196
+ const gamma_rad = (gamma * Math.PI) / 180;
197
+ const cos_alpha = Math.cos(alpha_rad);
198
+ const cos_beta = Math.cos(beta_rad);
199
+ const cos_gamma = Math.cos(gamma_rad);
200
+ const sin_gamma = Math.sin(gamma_rad);
201
+ // Calculate volume factor for triclinic system
202
+ const vol_factor = Math.sqrt(1 -
203
+ cos_alpha ** 2 -
204
+ cos_beta ** 2 -
205
+ cos_gamma ** 2 +
206
+ 2 * cos_alpha * cos_beta * cos_gamma);
207
+ // Standard crystallographic lattice vectors
208
+ const c1 = c * cos_beta;
209
+ const c2 = (c * (cos_alpha - cos_beta * cos_gamma)) / sin_gamma;
210
+ const c3 = (c * vol_factor) / sin_gamma;
211
+ return [
212
+ [a, 0, 0],
213
+ [b * cos_gamma, b * sin_gamma, 0],
214
+ [c1, c2, c3],
215
+ ];
216
+ }