Skip to content

Commit c3e1dc9

Browse files
committed
1. Update definition for input parameters
2. Update code to handle both single and multi-phase inputs 3. Add unit test for 2 phase example 4. Add example for 2 phase material
1 parent 0644f64 commit c3e1dc9

File tree

3 files changed

+173
-14
lines changed

3 files changed

+173
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import numpy as np
2+
from xrd_simulator.templates import polycrystal_from_odf
3+
4+
5+
# uniform orientation distribution function.
6+
def ODF(x, q): return 1. / (np.pi**2)
7+
8+
9+
number_of_crystals = 500
10+
bounding_height = 50.0
11+
bounding_radius = 25.0
12+
unit_cell = [[3.579, 3.579, 3.579, 90., 90., 90.0],
13+
[5.46745, 5.46745, 5.46745, 90., 90., 90.0]]
14+
sgname = ['F432', 'F432']
15+
max_bin = np.radians(10.0)
16+
path_to_cif_file = None
17+
18+
def strain_tensor(x): return np.array([[0, 0, 0.02 * x[2] / bounding_height],
19+
[0, 0, 0],
20+
[0, 0, 0]]) # Linear strain gradient along rotation axis.
21+
22+
23+
polycrystal = polycrystal_from_odf(ODF,
24+
number_of_crystals,
25+
bounding_height,
26+
bounding_radius,
27+
unit_cell,
28+
sgname,
29+
path_to_cif_file,
30+
max_bin,
31+
strain_tensor)

Diff for: tests/test_templates.py

+110
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,116 @@ def strain_tensor(x): return np.array(
175175
nosequences,
176176
10,
177177
msg="Few or no rings appeared from diffraction.")
178+
179+
def test_polycrystal_from_odf_2phases(self):
180+
181+
unit_cell = [[3.579, 3.579, 3.579, 90., 90., 90.0],
182+
[5.46745, 5.46745, 5.46745, 90., 90., 90.0]]
183+
sgname = ['F432', 'F432']
184+
185+
def orientation_density_function(
186+
x, q): return 1. / (np.pi**2) # uniform ODF
187+
number_of_crystals = 500
188+
sample_bounding_cylinder_height = 50
189+
sample_bounding_cylinder_radius = 25
190+
maximum_sampling_bin_seperation = np.radians(10.0)
191+
# Linear strain gradient along rotation axis.
192+
def strain_tensor(x): return np.array(
193+
[[0, 0, 0.02 * x[2] / sample_bounding_cylinder_height], [0, 0, 0], [0, 0, 0]])
194+
195+
polycrystal = templates.polycrystal_from_odf(
196+
orientation_density_function,
197+
number_of_crystals,
198+
sample_bounding_cylinder_height,
199+
sample_bounding_cylinder_radius,
200+
unit_cell,
201+
sgname,
202+
path_to_cif_file=None,
203+
maximum_sampling_bin_seperation=maximum_sampling_bin_seperation,
204+
strain_tensor=strain_tensor)
205+
206+
# Compare Euler angle distributions to scipy random uniform orientation
207+
# sampler
208+
euler1 = np.array([Rotation.from_matrix(U).as_euler(
209+
'xyz', degrees=True) for U in polycrystal.orientation_lab])
210+
euler2 = Rotation.random(10 * euler1.shape[0]).as_euler('xyz')
211+
212+
for i in range(3):
213+
hist1, bins = np.histogram(euler1[:, i])
214+
hist2, bins = np.histogram(euler2[:, i])
215+
hist2 = hist2 / 10.
216+
# These histograms should look roughly the same
217+
self.assertLessEqual(
218+
np.max(
219+
np.abs(
220+
hist1 -
221+
hist2)),
222+
number_of_crystals *
223+
0.05,
224+
"ODF not sampled correctly.")
225+
226+
parameters = {
227+
"detector_distance": 191023.9164,
228+
"detector_center_pixel_z": 256.2345,
229+
"detector_center_pixel_y": 255.1129,
230+
"pixel_side_length_z": 181.4234,
231+
"pixel_side_length_y": 180.2343,
232+
"number_of_detector_pixels_z": 512,
233+
"number_of_detector_pixels_y": 512,
234+
"wavelength": 0.285227,
235+
"beam_side_length_z": 512 * 200.,
236+
"beam_side_length_y": 512 * 200.,
237+
"rotation_step": np.radians(20.0),
238+
"rotation_axis": np.array([0., 0., 1.0])
239+
}
240+
241+
beam, detector, motion = templates.s3dxrd(parameters)
242+
243+
number_of_crystals = 100
244+
sample_bounding_cylinder_height = 256 * 180 / 128.
245+
sample_bounding_cylinder_radius = 256 * 180 / 128.
246+
247+
polycrystal = templates.polycrystal_from_odf(
248+
orientation_density_function,
249+
number_of_crystals,
250+
sample_bounding_cylinder_height,
251+
sample_bounding_cylinder_radius,
252+
unit_cell,
253+
sgname,
254+
path_to_cif_file=None,
255+
maximum_sampling_bin_seperation=maximum_sampling_bin_seperation,
256+
strain_tensor=strain_tensor)
257+
258+
polycrystal.transform(motion, time=0.134)
259+
polycrystal.diffract(
260+
beam,
261+
detector,
262+
motion,
263+
min_bragg_angle=0,
264+
max_bragg_angle=None,
265+
verbose=True)
266+
267+
diffraction_pattern = detector.render(
268+
frames_to_render=0,
269+
lorentz=False,
270+
polarization=False,
271+
structure_factor=False,
272+
method="centroid",
273+
verbose=True)
274+
bins, histogram = utils._diffractogram(
275+
diffraction_pattern, parameters['detector_center_pixel_z'], parameters['detector_center_pixel_y'])
276+
histogram[histogram < 0.5 * np.median(histogram)] = 0
277+
csequence, nosequences = 0, 0
278+
for i in range(histogram.shape[0]):
279+
if histogram[i] > 0:
280+
csequence += 1
281+
elif csequence >= 1:
282+
nosequences += 1
283+
csequence = 0
284+
self.assertGreaterEqual(
285+
nosequences,
286+
10,
287+
msg="Few or no rings appeared from diffraction.")
178288

179289
def test_get_uniform_powder_sample(self):
180290
sample_bounding_radius = 256 * 180 / 128.

Diff for: xrd_simulator/templates.py

+32-14
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,18 @@ def polycrystal_from_odf(
119119
sample_bounding_cylinder_radius (:obj:`float`): Radius of sample cylinder in units of
120120
microns.
121121
unit_cell (:obj:`list of lists` of :obj:`float`): Crystal unit cell representation of the form
122-
[[a,b,c,alpha,beta,gamma],], where alpha,beta and gamma are in units of degrees while
123-
a,b and c are in units of anstrom.
124-
sgname (:obj:`list of strings`): Name of space group , e.g ['P3221',] for quartz, SiO2, for instance
125-
path_to_cif_file (:obj:`list of strings`): [Path to CIF file,]. Defaults to None, in which case no structure
126-
factors are computed.
122+
[a,b,c,alpha,beta,gamma] or [[a,b,c,alpha,beta,gamma],], where alpha,beta and gamma are
123+
in units of degrees while a,b and c are in units of anstrom. When the unit_cell is just a
124+
list (first example), the input represents single-phase material. When the unit_cell is an
125+
iterable list (second example), the input represents multi-phase material.
126+
sgname (:obj:`list of strings`): Name of space group , e.g 'P3221' or ['P3221',] for quartz,
127+
SiO2, for instance. When the input is just a string, it represents space group for
128+
single-phase material. When the input is a list of string (second example), it represents
129+
space groups for multi-phase material.
130+
path_to_cif_file (:obj:`list of strings`): Path to CIF file or [Path to CIF file,].
131+
Defaults to None, in which case no structure factors are computed. When the input is a
132+
single file path (first example), the input is for single-phase material. When the
133+
input is a list of file paths (second example), the input is for multi-phase material.
127134
maximum_sampling_bin_seperation (:obj:`float`): Discretization steplength of orientation
128135
space using spherical coordinates over the unit quarternions in units of radians.
129136
A smaller steplength gives more accurate sampling of the input
@@ -172,17 +179,28 @@ def polycrystal_from_odf(
172179

173180
mesh = TetraMesh._build_tetramesh(cylinder)
174181

175-
phases = [Phase(uc, sg, cf) for uc, sg, cf in zip(unit_cell, sgname, path_to_cif_file)]
176-
if len(phases) == 1:
182+
if all(isinstance(x, list) for x in unit_cell) \
183+
and isinstance(sgname, list):
184+
# Multi Phase Material
185+
if not isinstance(path_to_cif_file, list):
186+
if path_to_cif_file is None:
187+
path_to_cif_file = [None]* len(unit_cell)
188+
else:
189+
print("Single cif file input. Please enter list of corresponding cif files.")
190+
exit
191+
phases = [Phase(uc, sg, cf) for uc, sg, cf in zip(unit_cell, sgname, path_to_cif_file)]
192+
if phase_fractions is None:
193+
# Sample is uniformly distributed phases
194+
element_phase_map = np.random.randint(len(phases), size=(mesh.number_of_elements,))
195+
else :
196+
# Sample is distributed by phase fraction
197+
probabilities = np.array(phase_fractions)
198+
element_phase_map = np.random.choice(len(phases), size=(mesh.number_of_elements,), p=probabilities)
199+
else:
200+
# Single Phase Material
201+
phases = [Phase(unit_cell, sgname, path_to_cif_file)]
177202
# Sample is uniformly single phase
178203
element_phase_map = np.zeros((mesh.number_of_elements,)).astype(int)
179-
elif phase_fractions is None:
180-
# Sample is uniformly distributed phases
181-
element_phase_map = np.random.randint(len(phases), size=(mesh.number_of_elements,))
182-
else :
183-
# Sample is distributed by phase fraction
184-
probabilities = np.array(phase_fractions)
185-
element_phase_map = np.random.choice(len(phases), size=(mesh.number_of_elements,), p=probabilities)
186204

187205
# Sample spatial texture
188206
orientation = _sample_ODF(

0 commit comments

Comments
 (0)