materialite 0.1.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.
- materialite/__init__.py +28 -0
- materialite/basis_operations.py +118 -0
- materialite/get_ipf_colors.py +199 -0
- materialite/importers.py +182 -0
- materialite/json_operations.py +122 -0
- materialite/material.py +1085 -0
- materialite/models/__init__.py +17 -0
- materialite/models/convolution_gpu_model.py +1408 -0
- materialite/models/convolution_microstructure_model.py +2097 -0
- materialite/models/convolution_model.py +1397 -0
- materialite/models/decoupled_crystal_elasticity.py +37 -0
- materialite/models/grain_coarsening_model.py +288 -0
- materialite/models/model.py +30 -0
- materialite/models/small_strain_fft/__init__.py +19 -0
- materialite/models/small_strain_fft/elastic.py +49 -0
- materialite/models/small_strain_fft/elastic_viscoplastic.py +280 -0
- materialite/models/small_strain_fft/hardening_laws.py +94 -0
- materialite/models/small_strain_fft/isotropic_elastic_plastic.py +187 -0
- materialite/models/small_strain_fft/load_schedule.py +119 -0
- materialite/models/small_strain_fft/multiphase.py +92 -0
- materialite/models/small_strain_fft/small_strain_fft.py +331 -0
- materialite/models/taylor_model.py +130 -0
- materialite/slip_system.py +173 -0
- materialite/tensor.py +1557 -0
- materialite/util/__init__.py +13 -0
- materialite/util/util.py +88 -0
- materialite-0.1.0.dist-info/METADATA +376 -0
- materialite-0.1.0.dist-info/RECORD +31 -0
- materialite-0.1.0.dist-info/WHEEL +5 -0
- materialite-0.1.0.dist-info/licenses/LICENSE +202 -0
- materialite-0.1.0.dist-info/top_level.txt +1 -0
materialite/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright 2025 United States Government as represented by the Administrator of the
|
|
2
|
+
# National Aeronautics and Space Administration. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# The Materialite platform is licensed under the Apache License, Version 2.0
|
|
5
|
+
# (the "License"); you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
9
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
11
|
+
# specific language governing permissions and limitations under the License.
|
|
12
|
+
|
|
13
|
+
# Skip isort on this file to avoid circular imports
|
|
14
|
+
# isort: skip_file
|
|
15
|
+
|
|
16
|
+
from .material import Material, Sphere, Superellipsoid, Feature, Box
|
|
17
|
+
from .tensor import (
|
|
18
|
+
Orientation,
|
|
19
|
+
Scalar,
|
|
20
|
+
Vector,
|
|
21
|
+
Order2Tensor,
|
|
22
|
+
Order2SymmetricTensor,
|
|
23
|
+
Order4SymmetricTensor,
|
|
24
|
+
)
|
|
25
|
+
from .slip_system import SlipSystem
|
|
26
|
+
from .json_operations import read_from_json, write_to_json
|
|
27
|
+
from .get_ipf_colors import get_ipf_colors, get_ipf, add_ipf_colors_field
|
|
28
|
+
from .importers import import_dream3d, import_evpfft, import_spparks, import_vgstudio
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Copyright 2025 United States Government as represented by the Administrator of the
|
|
2
|
+
# National Aeronautics and Space Administration. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# The Materialite platform is licensed under the Apache License, Version 2.0
|
|
5
|
+
# (the "License"); you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
9
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
11
|
+
# specific language governing permissions and limitations under the License.
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Not particularly liking calling this the natural basis but it is here for now
|
|
17
|
+
def natural_basis():
|
|
18
|
+
r2 = 1 / np.sqrt(2)
|
|
19
|
+
r3 = 1 / np.sqrt(3)
|
|
20
|
+
r6 = 1 / np.sqrt(6)
|
|
21
|
+
return np.array(
|
|
22
|
+
[
|
|
23
|
+
r3 * np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
|
|
24
|
+
r6 * np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 2]]),
|
|
25
|
+
r2 * np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 0]]),
|
|
26
|
+
r2 * np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]),
|
|
27
|
+
r2 * np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]),
|
|
28
|
+
r2 * np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]),
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def natural_product_basis():
|
|
34
|
+
return np.einsum("mij, nkl -> mnijkl", natural_basis(), natural_basis())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def mandel_basis():
|
|
38
|
+
r2 = 1 / np.sqrt(2)
|
|
39
|
+
return np.array(
|
|
40
|
+
[
|
|
41
|
+
[[1.0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
42
|
+
[[0, 0, 0], [0, 1.0, 0], [0, 0, 0]],
|
|
43
|
+
[[0, 0, 0], [0, 0, 0], [0, 0, 1.0]],
|
|
44
|
+
[[0, 0, 0], [0, 0, r2], [0, r2, 0]],
|
|
45
|
+
[[0, 0, r2], [0, 0, 0], [r2, 0, 0]],
|
|
46
|
+
[[0, r2, 0], [r2, 0, 0], [0, 0, 0]],
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def mandel_product_basis():
|
|
52
|
+
return np.einsum("mij, nkl -> mnijkl", mandel_basis(), mandel_basis())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def mises_basis():
|
|
56
|
+
r2 = 1 / np.sqrt(2)
|
|
57
|
+
r3 = 1 / np.sqrt(3)
|
|
58
|
+
r6 = 1 / np.sqrt(6)
|
|
59
|
+
return np.array(
|
|
60
|
+
[
|
|
61
|
+
r2 * np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 0]]),
|
|
62
|
+
r6 * np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 2]]),
|
|
63
|
+
r2 * np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]),
|
|
64
|
+
r2 * np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]),
|
|
65
|
+
r2 * np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]),
|
|
66
|
+
r3 * np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def mises_product_basis():
|
|
72
|
+
return np.einsum("mij, nkl -> mnijkl", mises_basis(), mises_basis())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def strain_voigt_basis():
|
|
76
|
+
return np.array(
|
|
77
|
+
[
|
|
78
|
+
np.array([[1, 0, 0], [0, 0, 0], [0, 0, 0]]),
|
|
79
|
+
np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]]),
|
|
80
|
+
np.array([[0, 0, 0], [0, 0, 0], [0, 0, 1]]),
|
|
81
|
+
0.5 * np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]),
|
|
82
|
+
0.5 * np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]),
|
|
83
|
+
0.5 * np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]),
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def stress_voigt_basis():
|
|
89
|
+
return np.array(
|
|
90
|
+
[
|
|
91
|
+
[[1.0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
92
|
+
[[0, 0, 0], [0, 1.0, 0], [0, 0, 0]],
|
|
93
|
+
[[0, 0, 0], [0, 0, 0], [0, 0, 1.0]],
|
|
94
|
+
[[0, 0, 0], [0, 0, 1.0], [0, 1.0, 0]],
|
|
95
|
+
[[0, 0, 1.0], [0, 0, 0], [1.0, 0, 0]],
|
|
96
|
+
[[0, 1.0, 0], [1.0, 0, 0], [0, 0, 0]],
|
|
97
|
+
]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def strain_voigt_dual_basis():
|
|
102
|
+
return stress_voigt_basis()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def stress_voigt_dual_basis():
|
|
106
|
+
return strain_voigt_basis()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def voigt_product_basis():
|
|
110
|
+
return np.einsum(
|
|
111
|
+
"mij, nkl -> mnijkl", stress_voigt_basis(), strain_voigt_dual_basis()
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def voigt_dual_product_basis():
|
|
116
|
+
return np.einsum(
|
|
117
|
+
"mij, nkl -> mnijkl", stress_voigt_dual_basis(), strain_voigt_basis()
|
|
118
|
+
)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Copyright 2025 United States Government as represented by the Administrator of the
|
|
2
|
+
# National Aeronautics and Space Administration. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# The Materialite platform is licensed under the Apache License, Version 2.0
|
|
5
|
+
# (the "License"); you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
9
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
11
|
+
# specific language governing permissions and limitations under the License.
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from materialite.tensor import Vector
|
|
15
|
+
from numpy.linalg import norm
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_ipf_colors_field(
|
|
19
|
+
material,
|
|
20
|
+
orientation_label="orientation",
|
|
21
|
+
specimen_frame_direction=Vector([0, 0, 1]),
|
|
22
|
+
unit_cell="cubic",
|
|
23
|
+
ipf_color_label="ipf_color",
|
|
24
|
+
):
|
|
25
|
+
orientations = material.extract(orientation_label)
|
|
26
|
+
ipf_colors = get_ipf_colors(specimen_frame_direction, orientations, unit_cell)
|
|
27
|
+
return material.create_fields({ipf_color_label: ipf_colors.tolist()})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_ipf_colors(specimen_frame_direction, orientations, unit_cell):
|
|
31
|
+
specimen_frame_direction = Vector(specimen_frame_direction)
|
|
32
|
+
direction = specimen_frame_direction / specimen_frame_direction.norm
|
|
33
|
+
crystal_directions = direction.to_crystal_frame(orientations)
|
|
34
|
+
ipf_points = _convert_to_fundamental_region(crystal_directions, unit_cell)
|
|
35
|
+
return _get_ipf_colors_from_fundamental_points(ipf_points, unit_cell)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_ipf(specimen_frame_direction, orientations, unit_cell):
|
|
39
|
+
specimen_frame_direction = Vector(specimen_frame_direction)
|
|
40
|
+
direction = specimen_frame_direction / specimen_frame_direction.norm
|
|
41
|
+
crystal_directions = direction.to_crystal_frame(orientations)
|
|
42
|
+
ipf_points_3D = _convert_to_fundamental_region(crystal_directions, unit_cell)
|
|
43
|
+
ipf_points = _get_stereographic_projection(ipf_points_3D)
|
|
44
|
+
ipf_boundary = _get_ipf_boundary(unit_cell)
|
|
45
|
+
return ipf_points, ipf_boundary
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _convert_to_fundamental_region(directions, unit_cell):
|
|
49
|
+
if unit_cell.lower() in ["cubic", "fcc", "bcc"]:
|
|
50
|
+
p = np.atleast_2d(directions.components)
|
|
51
|
+
p[p[:, 0] < 0, 0] *= -1
|
|
52
|
+
p[p[:, 1] < 0, 1] *= -1
|
|
53
|
+
p[p[:, 2] < 0, 2] *= -1
|
|
54
|
+
|
|
55
|
+
cond = p[:, 0] < p[:, 1]
|
|
56
|
+
p[cond, 0], p[cond, 1] = p[cond, 1], p[cond, 0].copy()
|
|
57
|
+
|
|
58
|
+
cond = p[:, 2] < p[:, 0]
|
|
59
|
+
p[cond, 2], p[cond, 0] = p[cond, 0], p[cond, 2].copy()
|
|
60
|
+
|
|
61
|
+
cond = p[:, 0] < p[:, 1]
|
|
62
|
+
p[cond, 0], p[cond, 1] = p[cond, 1], p[cond, 0].copy()
|
|
63
|
+
elif unit_cell.lower() in ["hcp", "hexagonal"]:
|
|
64
|
+
p = np.atleast_2d(directions.components).copy()
|
|
65
|
+
p[p[:, 0] < 0, 0] *= -1
|
|
66
|
+
p[p[:, 1] < 0, 1] *= -1
|
|
67
|
+
p[p[:, 2] < 0, 2] *= -1
|
|
68
|
+
angles = np.arctan2(p[:, 1], p[:, 0])
|
|
69
|
+
reduce_cond = angles >= np.pi / 3
|
|
70
|
+
c60 = 0.5
|
|
71
|
+
s60 = np.sqrt(3) / 2
|
|
72
|
+
reduce_R = np.array([[c60, s60], [-s60, c60]])
|
|
73
|
+
p[reduce_cond, :2] = np.einsum("ij, bj -> bi", reduce_R, p[reduce_cond, :2])
|
|
74
|
+
angles[reduce_cond] -= np.pi / 3
|
|
75
|
+
|
|
76
|
+
reflect_cond = angles >= np.pi / 6
|
|
77
|
+
reflect_R = np.array([[c60, s60], [s60, -c60]])
|
|
78
|
+
p[reflect_cond, :2] = np.einsum("ij, bj -> bi", reflect_R, p[reflect_cond, :2])
|
|
79
|
+
np.arctan2(p[:, 1], p[:, 0]) * 180 / np.pi
|
|
80
|
+
|
|
81
|
+
return p
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_ipf_colors_from_fundamental_points(points, unit_cell):
|
|
85
|
+
if unit_cell.lower() in ["cubic", "fcc", "bcc"]:
|
|
86
|
+
u = points[:, 2] - points[:, 0]
|
|
87
|
+
v = np.sqrt(2) * (points[:, 0] - points[:, 1])
|
|
88
|
+
w = np.sqrt(3) * points[:, 1]
|
|
89
|
+
uvw = np.c_[u, v, w]
|
|
90
|
+
elif unit_cell.lower() in ["hexagonal", "hcp"]:
|
|
91
|
+
u = points[:, 2]
|
|
92
|
+
v = points[:, 0] - np.sqrt(3) * points[:, 1]
|
|
93
|
+
w = 2 * points[:, 1]
|
|
94
|
+
uvw = np.c_[u, v, w]
|
|
95
|
+
return _adjust_colors(uvw)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _adjust_colors(uvw):
|
|
99
|
+
uvw = uvw / np.max(np.abs(uvw), axis=1, keepdims=True)
|
|
100
|
+
return np.sqrt(uvw)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _get_stereographic_projection(ipf_points):
|
|
104
|
+
azimuth, elevation = _cartesian_to_spherical(ipf_points)
|
|
105
|
+
P = 1.0 / np.cos(elevation / 2.0)
|
|
106
|
+
x_projected = P * np.sin(elevation / 2) * np.cos(azimuth)
|
|
107
|
+
y_projected = P * np.sin(elevation / 2) * np.sin(azimuth)
|
|
108
|
+
return np.c_[x_projected, y_projected]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _cartesian_to_spherical(xyz):
|
|
112
|
+
xy = xyz[:, 0] ** 2 + xyz[:, 1] ** 2
|
|
113
|
+
elevation = np.arctan2(np.sqrt(xy), xyz[:, 2])
|
|
114
|
+
azimuth = np.arctan2(xyz[:, 1], xyz[:, 0])
|
|
115
|
+
return azimuth, elevation
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _get_ipf_boundary(unit_cell):
|
|
119
|
+
num_points = 1001
|
|
120
|
+
if unit_cell.lower() in ["cubic", "fcc", "bcc"]:
|
|
121
|
+
great_circle_xz = np.ones(num_points)
|
|
122
|
+
great_circle_points_3D = np.c_[
|
|
123
|
+
great_circle_xz, np.linspace(0, 1, num_points), great_circle_xz
|
|
124
|
+
]
|
|
125
|
+
great_circle_points_3D = great_circle_points_3D / norm(
|
|
126
|
+
great_circle_points_3D, axis=1, keepdims=True
|
|
127
|
+
)
|
|
128
|
+
great_circle_points = _get_stereographic_projection(great_circle_points_3D)
|
|
129
|
+
ipf_boundary = np.concatenate([[[0, 0]], great_circle_points, [[0, 0]]], axis=0)
|
|
130
|
+
elif unit_cell.lower() in ["hexagonal", "hcp"]:
|
|
131
|
+
pole_1 = np.array([1, 0, 0])
|
|
132
|
+
pole_2 = np.array([np.sqrt(3) / 2, 0.5, 0])
|
|
133
|
+
great_circle_direction = pole_2 - pole_1
|
|
134
|
+
increments = np.linspace(0, 1, num_points)
|
|
135
|
+
great_circle_points_3D = (
|
|
136
|
+
increments[:, np.newaxis] * great_circle_direction + pole_1
|
|
137
|
+
)
|
|
138
|
+
great_circle_points_3D = great_circle_points_3D / norm(
|
|
139
|
+
great_circle_points_3D, axis=1, keepdims=True
|
|
140
|
+
)
|
|
141
|
+
great_circle_points = _get_stereographic_projection(great_circle_points_3D)
|
|
142
|
+
ipf_boundary = np.concatenate([[[0, 0]], great_circle_points, [[0, 0]]], axis=0)
|
|
143
|
+
return ipf_boundary
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_symmetry_operators(unit_cell):
|
|
147
|
+
# source: Rollett lecture notes on symmetry; corrected from Kocks, Tome,
|
|
148
|
+
# and Wenk, Texture and Anisotropy
|
|
149
|
+
i = np.array([1, 0, 0])
|
|
150
|
+
j = np.array([0, 1, 0])
|
|
151
|
+
k = np.array([0, 0, 1])
|
|
152
|
+
if unit_cell.lower() in ["hexagonal", "hcp"]:
|
|
153
|
+
a = np.sqrt(3) / 2
|
|
154
|
+
p5 = 0.5
|
|
155
|
+
m5 = -0.5
|
|
156
|
+
symmetry_matrices = [0] * 12
|
|
157
|
+
symmetry_matrices[0] = np.eye(3)
|
|
158
|
+
symmetry_matrices[1] = np.array([[m5, a, 0], [-a, m5, 0], k])
|
|
159
|
+
symmetry_matrices[2] = np.array([[m5, -a, 0], [a, m5, 0], k])
|
|
160
|
+
symmetry_matrices[3] = np.array([[p5, a, 0], [-a, p5, 0], k])
|
|
161
|
+
symmetry_matrices[4] = np.array([-i, -j, k])
|
|
162
|
+
symmetry_matrices[5] = np.array([[p5, -a, 0], [a, p5, 0], k])
|
|
163
|
+
symmetry_matrices[6] = np.array([[m5, -a, 0], [-a, p5, 0], -k])
|
|
164
|
+
symmetry_matrices[7] = np.array([i, -j, -k])
|
|
165
|
+
symmetry_matrices[8] = np.array([[m5, a, 0], [a, p5, 0], -k])
|
|
166
|
+
symmetry_matrices[9] = np.array([[p5, a, 0], [a, m5, 0], -k])
|
|
167
|
+
symmetry_matrices[10] = np.array([-i, j, -k])
|
|
168
|
+
symmetry_matrices[11] = np.array([[p5, -a, 0], [-a, m5, 0], -k])
|
|
169
|
+
|
|
170
|
+
elif unit_cell.lower() in ["cubic", "fcc", "bcc"]:
|
|
171
|
+
symmetry_matrices = [0] * 24
|
|
172
|
+
symmetry_matrices[0] = np.eye(3)
|
|
173
|
+
symmetry_matrices[1] = np.array([k, i, j])
|
|
174
|
+
symmetry_matrices[2] = np.array([j, k, i])
|
|
175
|
+
symmetry_matrices[3] = np.array([-j, k, -i])
|
|
176
|
+
symmetry_matrices[4] = np.array([-j, -k, i])
|
|
177
|
+
symmetry_matrices[5] = np.array([j, -k, -i])
|
|
178
|
+
symmetry_matrices[6] = np.array([-k, i, -j])
|
|
179
|
+
symmetry_matrices[7] = np.array([-k, -i, j])
|
|
180
|
+
symmetry_matrices[8] = np.array([k, -i, -j])
|
|
181
|
+
symmetry_matrices[9] = np.array([-i, j, -k])
|
|
182
|
+
symmetry_matrices[10] = np.array([-i, -j, k])
|
|
183
|
+
symmetry_matrices[11] = np.array([i, -j, -k])
|
|
184
|
+
symmetry_matrices[12] = np.array([-k, -j, -i])
|
|
185
|
+
symmetry_matrices[13] = np.array([k, -j, i])
|
|
186
|
+
symmetry_matrices[14] = np.array([k, j, -i])
|
|
187
|
+
symmetry_matrices[15] = np.array([-k, j, i])
|
|
188
|
+
symmetry_matrices[16] = np.array([-i, -k, -j])
|
|
189
|
+
symmetry_matrices[17] = np.array([i, -k, j])
|
|
190
|
+
symmetry_matrices[18] = np.array([i, k, -j])
|
|
191
|
+
symmetry_matrices[19] = np.array([-i, k, j])
|
|
192
|
+
symmetry_matrices[20] = np.array([-j, -i, -k])
|
|
193
|
+
symmetry_matrices[21] = np.array([j, -i, k])
|
|
194
|
+
symmetry_matrices[22] = np.array([j, i, -k])
|
|
195
|
+
symmetry_matrices[23] = np.array([-j, i, k])
|
|
196
|
+
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(f"{unit_cell} is not a valid unit cell")
|
|
199
|
+
return np.array(symmetry_matrices)
|
materialite/importers.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Copyright 2025 United States Government as represented by the Administrator of the
|
|
2
|
+
# National Aeronautics and Space Administration. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# The Materialite platform is licensed under the Apache License, Version 2.0
|
|
5
|
+
# (the "License"); you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
9
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
11
|
+
# specific language governing permissions and limitations under the License.
|
|
12
|
+
|
|
13
|
+
import h5py
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from materialite import Material
|
|
17
|
+
from materialite.util import camel_to_snake
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def import_dream3d(
|
|
21
|
+
file,
|
|
22
|
+
simpl_geometry_path,
|
|
23
|
+
field_paths=None,
|
|
24
|
+
region_id_path=None,
|
|
25
|
+
region_field_paths=None,
|
|
26
|
+
):
|
|
27
|
+
if region_id_path is None and region_field_paths is not None:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"region_id_path must be specified if region_field_paths are provided"
|
|
30
|
+
)
|
|
31
|
+
d3d = h5py.File(file, mode="r")
|
|
32
|
+
|
|
33
|
+
simpl_geometry_data = d3d.get(simpl_geometry_path)
|
|
34
|
+
|
|
35
|
+
dimensions = list(simpl_geometry_data.get("DIMENSIONS"))
|
|
36
|
+
num_points = np.prod(dimensions)
|
|
37
|
+
origin = list(simpl_geometry_data.get("ORIGIN"))
|
|
38
|
+
spacing = list(simpl_geometry_data.get("SPACING"))
|
|
39
|
+
|
|
40
|
+
fields = _initialize_fields(dimensions, origin, spacing)
|
|
41
|
+
|
|
42
|
+
field_paths = [] if field_paths is None else field_paths
|
|
43
|
+
for field_path in field_paths:
|
|
44
|
+
label = camel_to_snake(field_path.split("/")[-1])
|
|
45
|
+
field = d3d.get(field_path)[:]
|
|
46
|
+
field = field.reshape((num_points, field.shape[-1]))
|
|
47
|
+
num_components = field.shape[1]
|
|
48
|
+
for ii in range(num_components):
|
|
49
|
+
suffix = "" if num_components == 1 else "_" + str(ii + 1)
|
|
50
|
+
fields[label + suffix] = field[:, ii]
|
|
51
|
+
|
|
52
|
+
regional_fields = {}
|
|
53
|
+
if region_id_path is not None:
|
|
54
|
+
region_id_label = camel_to_snake(region_id_path.split("/")[-1])
|
|
55
|
+
region_id_field = d3d.get(region_id_path)[:].reshape(-1)
|
|
56
|
+
fields[region_id_label] = region_id_field
|
|
57
|
+
if region_field_paths is not None:
|
|
58
|
+
regional_fields[region_id_label] = np.unique(region_id_field)
|
|
59
|
+
|
|
60
|
+
region_field_paths = [] if region_field_paths is None else region_field_paths
|
|
61
|
+
for region_field_path in region_field_paths:
|
|
62
|
+
region_label = camel_to_snake(region_field_path.split("/")[-1])
|
|
63
|
+
region_field = d3d.get(region_field_path)[:]
|
|
64
|
+
num_components = region_field.shape[1]
|
|
65
|
+
for ii in range(num_components):
|
|
66
|
+
suffix = "" if num_components == 1 else "_" + str(ii + 1)
|
|
67
|
+
regional_fields[region_label + suffix] = region_field[1:, ii]
|
|
68
|
+
|
|
69
|
+
material = Material(
|
|
70
|
+
dimensions=dimensions, origin=origin, spacing=spacing, fields=fields
|
|
71
|
+
)
|
|
72
|
+
if regional_fields:
|
|
73
|
+
material = material.create_regional_fields(
|
|
74
|
+
region_label=region_id_label, regional_fields=regional_fields
|
|
75
|
+
)
|
|
76
|
+
return material
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _initialize_fields(dimensions, origin, spacing):
|
|
80
|
+
grid = np.indices(dimensions).T.reshape(-1, 3)
|
|
81
|
+
return pd.DataFrame(
|
|
82
|
+
{
|
|
83
|
+
"x": grid[:, 0] * spacing[0] + origin[0],
|
|
84
|
+
"y": grid[:, 1] * spacing[1] + origin[1],
|
|
85
|
+
"z": grid[:, 2] * spacing[2] + origin[2],
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def import_spparks(
|
|
91
|
+
file,
|
|
92
|
+
usecols=[1, 2, 3, 4],
|
|
93
|
+
names=["feature", "x", "y", "z"],
|
|
94
|
+
skiprows=9,
|
|
95
|
+
voxel_size=1,
|
|
96
|
+
):
|
|
97
|
+
fields = pd.read_csv(
|
|
98
|
+
file,
|
|
99
|
+
sep=" ",
|
|
100
|
+
header=None,
|
|
101
|
+
skiprows=skiprows,
|
|
102
|
+
usecols=[1, 2, 3, 4],
|
|
103
|
+
names=["feature", "x", "y", "z"],
|
|
104
|
+
)
|
|
105
|
+
fields.sort_values(by=["z", "y", "x"], inplace=True)
|
|
106
|
+
|
|
107
|
+
dimensions = [fields[dim].max() - fields[dim].min() + 1 for dim in ["x", "y", "z"]]
|
|
108
|
+
|
|
109
|
+
origin = [0, 0, 0]
|
|
110
|
+
fields[["x", "y", "z"]] = voxel_size * fields[["x", "y", "z"]]
|
|
111
|
+
spacing = [voxel_size, voxel_size, voxel_size]
|
|
112
|
+
|
|
113
|
+
return Material(
|
|
114
|
+
dimensions=dimensions, origin=origin, spacing=spacing, fields=fields
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def import_vgstudio(
|
|
119
|
+
file,
|
|
120
|
+
dimensions,
|
|
121
|
+
label="xct",
|
|
122
|
+
spacing=[1, 1, 1],
|
|
123
|
+
origin=[0, 0, 0],
|
|
124
|
+
dtype=np.float32,
|
|
125
|
+
x_range=None,
|
|
126
|
+
y_range=None,
|
|
127
|
+
z_range=None,
|
|
128
|
+
):
|
|
129
|
+
if x_range is None:
|
|
130
|
+
x_range = [0, dimensions[0]]
|
|
131
|
+
if y_range is None:
|
|
132
|
+
y_range = [0, dimensions[1]]
|
|
133
|
+
if z_range is None:
|
|
134
|
+
z_range = [0, dimensions[2]]
|
|
135
|
+
|
|
136
|
+
field = np.fromfile(file, dtype)
|
|
137
|
+
field = field.reshape(np.array(dimensions)[::-1])
|
|
138
|
+
field = field[
|
|
139
|
+
z_range[0] : z_range[1], y_range[0] : y_range[1], x_range[0] : x_range[1]
|
|
140
|
+
]
|
|
141
|
+
dimensions = field.shape[::-1]
|
|
142
|
+
|
|
143
|
+
return Material(
|
|
144
|
+
dimensions=dimensions,
|
|
145
|
+
spacing=spacing,
|
|
146
|
+
origin=origin,
|
|
147
|
+
fields={label: field.ravel()},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def import_evpfft(
|
|
152
|
+
file,
|
|
153
|
+
spacing=[1, 1, 1],
|
|
154
|
+
origin=[0, 0, 0],
|
|
155
|
+
):
|
|
156
|
+
|
|
157
|
+
fields = pd.read_csv(
|
|
158
|
+
file,
|
|
159
|
+
header=None,
|
|
160
|
+
delimiter=" ",
|
|
161
|
+
names=[
|
|
162
|
+
"euler_angle_1",
|
|
163
|
+
"euler_angle_2",
|
|
164
|
+
"euler_angle_3",
|
|
165
|
+
"x_id",
|
|
166
|
+
"y_id",
|
|
167
|
+
"z_id",
|
|
168
|
+
"feature",
|
|
169
|
+
"phase",
|
|
170
|
+
],
|
|
171
|
+
).sort_values(by=["x_id", "y_id", "z_id"])
|
|
172
|
+
|
|
173
|
+
fields[["euler_angle_1", "euler_angle_2", "euler_angle_3"]] *= np.pi / 180
|
|
174
|
+
|
|
175
|
+
dimensions = fields[["x_id", "y_id", "z_id"]].max().to_numpy()
|
|
176
|
+
|
|
177
|
+
return Material(
|
|
178
|
+
dimensions=dimensions,
|
|
179
|
+
spacing=spacing,
|
|
180
|
+
origin=origin,
|
|
181
|
+
fields=fields,
|
|
182
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright 2025 United States Government as represented by the Administrator of the
|
|
2
|
+
# National Aeronautics and Space Administration. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# The Materialite platform is licensed under the Apache License, Version 2.0
|
|
5
|
+
# (the "License"); you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
9
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
11
|
+
# specific language governing permissions and limitations under the License.
|
|
12
|
+
|
|
13
|
+
import gzip
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
import materialite.tensor
|
|
17
|
+
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
19
|
+
from materialite import Material
|
|
20
|
+
from materialite.tensor import Orientation, Tensor
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MaterialEncoder(json.JSONEncoder):
|
|
24
|
+
def default(self, obj):
|
|
25
|
+
if isinstance(obj, Tensor):
|
|
26
|
+
return {
|
|
27
|
+
"data": obj.components.tolist(),
|
|
28
|
+
"type": type(obj).__name__,
|
|
29
|
+
"dims": obj.dims_str,
|
|
30
|
+
}
|
|
31
|
+
elif isinstance(obj, Orientation):
|
|
32
|
+
return {"data": obj.rotation_matrix.tolist(), "type": type(obj).__name__}
|
|
33
|
+
elif isinstance(obj, (np.number, np.ndarray)):
|
|
34
|
+
return {"data": obj.tolist()}
|
|
35
|
+
return super().default(obj)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def decoder_object_hook(dct):
|
|
39
|
+
data = dct.get("data", None)
|
|
40
|
+
if data is None:
|
|
41
|
+
return dct
|
|
42
|
+
data_type = dct.get("type", None)
|
|
43
|
+
dims = dct.get("dims", None)
|
|
44
|
+
if data_type is None:
|
|
45
|
+
return data
|
|
46
|
+
elif dims is None:
|
|
47
|
+
tensor_type = getattr(materialite.tensor, data_type)
|
|
48
|
+
return tensor_type(data)
|
|
49
|
+
else:
|
|
50
|
+
tensor_type = getattr(materialite.tensor, data_type)
|
|
51
|
+
return tensor_type(data, dims)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read_from_json(filename, decompress=False):
|
|
55
|
+
if decompress:
|
|
56
|
+
r_string = "rb"
|
|
57
|
+
else:
|
|
58
|
+
r_string = "r"
|
|
59
|
+
with open(filename, r_string) as f:
|
|
60
|
+
loaded_data = f.read()
|
|
61
|
+
if decompress:
|
|
62
|
+
loaded_data = gzip.decompress(loaded_data).decode("utf-8")
|
|
63
|
+
loaded_data = json.loads(loaded_data, object_hook=decoder_object_hook)
|
|
64
|
+
state = dict()
|
|
65
|
+
fields = dict()
|
|
66
|
+
regional_fields = dict()
|
|
67
|
+
for k, v in loaded_data.items():
|
|
68
|
+
if k == "metadata":
|
|
69
|
+
origin = v["origin"]
|
|
70
|
+
dimensions = v["dimensions"]
|
|
71
|
+
spacing = v["spacing"]
|
|
72
|
+
continue
|
|
73
|
+
elif k == "state":
|
|
74
|
+
state = v
|
|
75
|
+
elif k == "regional_fields":
|
|
76
|
+
regional_fields = v
|
|
77
|
+
else:
|
|
78
|
+
fields[k] = v
|
|
79
|
+
if not fields:
|
|
80
|
+
material = Material(dimensions=dimensions, origin=origin, spacing=spacing)
|
|
81
|
+
else:
|
|
82
|
+
fields = pd.DataFrame(fields)
|
|
83
|
+
material = Material(
|
|
84
|
+
dimensions=dimensions, origin=origin, spacing=spacing, fields=fields
|
|
85
|
+
)
|
|
86
|
+
material.state = state
|
|
87
|
+
for k, v in regional_fields.items():
|
|
88
|
+
material = material.create_regional_fields(k, pd.DataFrame(v))
|
|
89
|
+
return material
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def write_to_json(material, filename, compress=False):
|
|
93
|
+
coord_labels = ["x", "y", "z", "x_id", "y_id", "z_id"]
|
|
94
|
+
labels = list(material.fields.drop(coord_labels, axis=1))
|
|
95
|
+
material_dict = {
|
|
96
|
+
"metadata": {
|
|
97
|
+
"origin": material.origin.tolist(),
|
|
98
|
+
"dimensions": material.dimensions.tolist(),
|
|
99
|
+
"spacing": material.spacing.tolist(),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if material.state:
|
|
103
|
+
material_dict["state"] = material.state
|
|
104
|
+
if material._regional_fields:
|
|
105
|
+
material_dict["regional_fields"] = dict()
|
|
106
|
+
for k in material._regional_fields.keys():
|
|
107
|
+
regional_labels = list(material.extract_regional_field(k))
|
|
108
|
+
material_dict["regional_fields"][k] = dict()
|
|
109
|
+
for l in regional_labels:
|
|
110
|
+
material_dict["regional_fields"][k][l] = (
|
|
111
|
+
material.extract_regional_field(k, l)
|
|
112
|
+
)
|
|
113
|
+
for l in labels:
|
|
114
|
+
material_dict[l] = material.extract(l)
|
|
115
|
+
json_material = json.dumps(material_dict, cls=MaterialEncoder)
|
|
116
|
+
if compress:
|
|
117
|
+
json_material = gzip.compress(json_material.encode("utf-8"))
|
|
118
|
+
w_string = "wb"
|
|
119
|
+
else:
|
|
120
|
+
w_string = "w"
|
|
121
|
+
with open(filename, w_string) as f:
|
|
122
|
+
f.write(json_material)
|