draw_bravais_lattice.py ダウンロード/コピー
draw_bravais_lattice.py をダウンロード
draw_bravais_lattice.py
draw_bravais_lattice.py
1"""
2ブラベー格子の単位格子と格子点を3Dで可視化するスクリプト。
3
4このスクリプトは、指定された格子定数と結晶系に基づいて、
5ブラベー格子の単位格子、格子点、格子ベクトル、およびオプションで補助線や基本格子を3Dで描画します。
6Matplotlibを使用して3Dプロットを生成し、結果を画像ファイルとして保存し、表示します。
7
8関連リンク: :doc:`draw_bravais_lattice_usage`
9"""
10import sys
11import numpy as np
12import matplotlib.pyplot as plt
13from mpl_toolkits.mplot3d import Axes3D
14from matplotlib.patches import FancyArrowPatch
15from mpl_toolkits.mplot3d import Axes3D, proj3d
16
17# 始点の初期値
18elev = 5.90
19azim = -67.55
20
21draw_lattice_vectors = True
22draw_support_lines = True
23draw_primitive_cell = False
24
25# ブラべー格子
26lattice_vector_color = 'blue'
27axis_arrow_color = 'blue'
28axis_label_font_size = 24
29# 格子点
30point_color = 'red'
31point_size = 500
32
33#cell = 'SC'
34#cell = 'ST'
35#cell = 'SO'
36#cell = 'SR'
37#cell = 'SH'
38#cell = 'FC'
39#cell = 'FO'
40cell = 'BC'
41#cell = 'BT'
42#cell = 'BO'
43#cell = 'CO'
44#cell = 'SM'
45#cell = 'STri'
46#cell = 'CM'
47
48
49class Arrow3D(FancyArrowPatch):
50 """
51 MatplotlibのFancyArrowPatchを継承し、3D空間に矢印を描画するためのクラス。
52
53 このクラスは、3D座標の矢印を2Dのスクリーン座標に投影し、
54 Matplotlibの3D軸上に適切に描画することを可能にします。
55 """
56 def __init__(self, xs, ys, zs, *args, **kwargs):
57 """
58 Arrow3Dオブジェクトを初期化します。
59
60 :param xs: 矢印のX座標 (開始点, 終了点)。
61 :type xs: list[float]
62 :param ys: 矢印のY座標 (開始点, 終了点)。
63 :type ys: list[float]
64 :param zs: 矢印のZ座標 (開始点, 終了点)。
65 :type zs: list[float]
66 :param args: FancyArrowPatchに渡される追加の位置引数。
67 :param kwargs: FancyArrowPatchに渡される追加のキーワード引数。
68 """
69 super().__init__((0, 0), (0, 0), *args, **kwargs)
70 self._verts3d = xs, ys, zs
71
72 def draw(self, renderer):
73 """
74 矢印をレンダラーに描画します。
75
76 3D座標を2Dスクリーン座標に変換し、変換された座標に基づいて矢印を描画します。
77
78 :param renderer: 描画に使用されるレンダラーオブジェクト。
79 :type renderer: matplotlib.backend_bases.RendererBase
80 """
81 xs3d, ys3d, zs3d = self._verts3d
82 proj = self.axes.get_proj()
83 xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, proj)
84 self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
85 super().draw(renderer)
86
87 def do_3d_projection(self, renderer=None):
88 """
89 3D投影を計算し、描画順序のためのZ深度を返します。
90
91 矢印の3D座標を2Dスクリーン座標に変換し、最小のZ深度を返して、
92 Matplotlibの3Dレンダリングにおける描画順序を適切に処理します。
93
94 :param renderer: 描画に使用されるレンダラーオブジェクト(オプション)。
95 :type renderer: matplotlib.backend_bases.RendererBase or None
96 :returns: 変換されたZ座標の最小値。
97 :rtype: float
98 """
99 xs3d, ys3d, zs3d = self._verts3d
100 proj = self.axes.get_proj()
101 xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, proj)
102 self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
103 return np.min(zs)
104
105
106def draw_vector(ax, vec, color, label, fontsize):
107 """
108 指定された3Dベクトルを矢印としてMatplotlibの3D軸に描画します。
109
110 矢印は原点(0,0,0)から指定されたベクトルまで描画され、
111 ベクトルの先端にはラベルが表示されます。
112
113 :param ax: 矢印を描画する3D軸オブジェクト。
114 :type ax: mpl_toolkits.mplot3d.axes3d.Axes3D
115 :param vec: 矢印の終点となる3Dベクトル。
116 :type vec: numpy.ndarray
117 :param color: 矢印とラベルの色。
118 :type color: str
119 :param label: 矢印のラベル文字列。
120 :type label: str
121 :param fontsize: ラベルのフォントサイズ。
122 :type fontsize: int
123 :returns: なし
124 """
125 arrow = Arrow3D([0, vec[0]], [0, vec[1]], [0, vec[2]],
126 mutation_scale=20, lw=2, arrowstyle="-|>", color=color)
127 ax.add_artist(arrow)
128 ax.text(vec[0] * 1.05, vec[1] * 1.05, vec[2] * 1.05, label, fontsize=fontsize, color=color)
129
130
131def lattice_vectors(a, b, c, alpha, beta, gamma):
132 """
133 単位格子の格子定数から3つの基本格子ベクトルを計算します。
134
135 計算は、a軸をx軸に沿わせ、b軸をxy平面に置き、c軸をz軸正方向に向ける形で
136 座標系を設定して行われます。
137
138 :param a: 格子定数aの長さ。
139 :type a: float
140 :param b: 格子定数bの長さ。
141 :type b: float
142 :param c: 格子定数cの長さ。
143 :type c: float
144 :param alpha: 角度α(bとcの間の角度、度数)。
145 :type alpha: float
146 :param beta: 角度β(aとcの間の角度、度数)。
147 :type beta: float
148 :param gamma: 角度γ(aとbの間の角度、度数)。
149 :type gamma: float
150 :returns: 3つの基本格子ベクトル (va, vb, vc)。
151 :rtype: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]
152 """
153 alpha_r, beta_r, gamma_r = np.radians([alpha, beta, gamma])
154 va = np.array([a, 0, 0])
155 vb = np.array([b * np.cos(gamma_r), b * np.sin(gamma_r), 0])
156 cx = c * np.cos(beta_r)
157 cy = c * (np.cos(alpha_r) - np.cos(beta_r) * np.cos(gamma_r)) / np.sin(gamma_r)
158 cz = np.sqrt(c**2 - cx**2 - cy**2)
159 vc = np.array([cx, cy, cz])
160 return va, vb, vc
161
162
163def set_equal_aspect(ax, points):
164 """
165 Matplotlibの3Dプロットの軸のアスペクト比を等しく設定します。
166
167 描画される全ての点の範囲に基づいて、x, y, z軸の表示範囲を調整し、
168 立体が歪まないように真のアスペクト比を維持します。
169
170 :param ax: 軸のアスペクト比を設定する3D軸オブジェクト。
171 :type ax: mpl_toolkits.mplot3d.axes3d.Axes3D
172 :param points: 描画される全ての点のNumpy配列。
173 :type points: numpy.ndarray
174 :returns: なし
175 """
176 xlim = [np.min(points[:, 0]), np.max(points[:, 0])]
177 ylim = [np.min(points[:, 1]), np.max(points[:, 1])]
178 zlim = [np.min(points[:, 2]), np.max(points[:, 2])]
179 max_range = max(
180 xlim[1] - xlim[0],
181 ylim[1] - ylim[0],
182 zlim[1] - zlim[0]
183 ) / 2
184
185 mid_x = np.mean(xlim)
186 mid_y = np.mean(ylim)
187 mid_z = np.mean(zlim)
188
189 ax.set_xlim(mid_x - max_range, mid_x + max_range)
190 ax.set_ylim(mid_y - max_range, mid_y + max_range)
191 ax.set_zlim(mid_z - max_range, mid_z + max_range)
192 ax.set_box_aspect([1, 1, 1])
193
194
195def draw_unit_cell_with_lattice(
196 a, b, c, alpha, beta, gamma,
197 lattice_points=None,
198 dashed_lines=None,
199 basis_vectors=None
200):
201 """
202 指定された格子定数と格子点を用いて、ブラベー単位格子を3Dで描画します。
203
204 この関数は、単位格子の辺、頂点、格子ベクトル、格子点、オプションで補助線、
205 および基本格子を描画し、結果を画像ファイルとして保存した後、画面に表示します。
206 ユーザーがプロットを操作すると、現在の視点角度 (elev, azim) がコンソールに出力されます。
207
208 :param a: 格子定数aの長さ。
209 :type a: float
210 :param b: 格子定数bの長さ。
211 :type b: float
212 :param c: 格子定数cの長さ。
213 :type c: float
214 :param alpha: 角度α(bとcの間の角度、度数)。
215 :type alpha: float
216 :param beta: 角度β(aとcの間の角度、度数)。
217 :type beta: float
218 :param gamma: 角度γ(aとbの間の角度、度数)。
219 :type gamma: float
220 :param lattice_points: 格子点を表す係数のリスト。各要素は `[u, v, w]` 形式で `u*va + v*vb + w*vc` と計算されます。
221 Noneの場合、格子点は描画されません。
222 :type lattice_points: list[list[float]] or None
223 :param dashed_lines: 補助線(破線)の始点と終点を表す係数のリスト。各要素は `([u1, v1, w1], [u2, v2, w2])` 形式です。
224 Noneの場合、補助線は描画されません。
225 :type dashed_lines: list[tuple[tuple[float, float, float], tuple[float, float, float]]] or None
226 :param basis_vectors: 基本格子ベクトルを表す係数のリスト。各要素は `[u, v, w]` 形式です。
227 Noneの場合、基本格子は描画されません。
228 :type basis_vectors: list[list[float]] or None
229 :returns: なし
230 """
231 va, vb, vc = lattice_vectors(a, b, c, alpha, beta, gamma)
232 origin = np.array([0, 0, 0])
233 corners = [
234 origin,
235 va,
236 vb,
237 vc,
238 va + vb,
239 va + vc,
240 vb + vc,
241 va + vb + vc
242 ]
243 corners = np.array(corners)
244
245 edges = [
246 (0, 1), (0, 2), (0, 3),
247 (1, 4), (1, 5),
248 (2, 4), (2, 6),
249 (3, 5), (3, 6),
250 (4, 7), (5, 7), (6, 7)
251 ]
252
253 fig = plt.figure(figsize=(8, 6))
254 ax = fig.add_subplot(111, projection='3d')
255 ax.view_init(elev=elev, azim=azim)
256
257 def on_draw(event):
258 elev_now = ax.elev
259 azim_now = ax.azim
260 print(f"elev: {elev_now:.2f}, azim: {azim_now:.2f}")
261
262 fig.canvas.mpl_connect('draw_event', on_draw)
263
264 # 単位格子の辺
265 for i, j in edges:
266 ax.plot(*zip(corners[i], corners[j]), color='black', linewidth=0.5)
267
268 # 単位格子の頂点
269 ax.scatter(corners[:, 0], corners[:, 1], corners[:, 2], color='black', s=20)
270
271 # 格子ベクトル
272 if draw_lattice_vectors:
273 for vec, label in zip([va, vb, vc], ['a', 'b', 'c']):
274 draw_vector(ax, vec, lattice_vector_color, label, axis_label_font_size)
275
276 # 格子点の描画
277 real_points = []
278 if lattice_points:
279 real_points = [p[0] * va + p[1] * vb + p[2] * vc for p in lattice_points]
280 real_points = np.array(real_points)
281 ax.scatter(real_points[:, 0], real_points[:, 1], real_points[:, 2],
282 color=point_color, s=point_size)
283
284 # 補助線(鎖線)
285 if draw_support_lines and dashed_lines:
286 for p1, p2 in dashed_lines:
287 pt1 = p1[0] * va + p1[1] * vb + p1[2] * vc
288 pt2 = p2[0] * va + p2[1] * vb + p2[2] * vc
289 ax.plot(*zip(pt1, pt2), color='gray', linestyle='dashed', linewidth=0.8)
290
291 # 基本格子
292 if draw_primitive_cell and basis_vectors:
293 for vec in basis_vectors:
294 v = vec[0] * va + vec[1] * vb + vec[2] * vc
295 ax.quiver(0, 0, 0, *v, color='green', linewidth=1.5, arrow_length_ratio=0.2)
296
297 if len(basis_vectors) == 3:
298 bv1 = basis_vectors[0][0] * va + basis_vectors[0][1] * vb + basis_vectors[0][2] * vc
299 bv2 = basis_vectors[1][0] * va + basis_vectors[1][1] * vb + basis_vectors[1][2] * vc
300 bv3 = basis_vectors[2][0] * va + basis_vectors[2][1] * vb + basis_vectors[2][2] * vc
301
302 base_points = [
303 origin,
304 bv1,
305 bv2,
306 bv3,
307 bv1 + bv2,
308 bv1 + bv3,
309 bv2 + bv3,
310 bv1 + bv2 + bv3
311 ]
312
313 base_edges = [
314 (0, 1), (0, 2), (0, 3),
315 (1, 4), (1, 5),
316 (2, 4), (2, 6),
317 (3, 5), (3, 6),
318 (4, 7), (5, 7), (6, 7)
319 ]
320
321 for i, j in base_edges:
322 ax.plot(*zip(base_points[i], base_points[j]),
323 color='lightgreen', linewidth=1.2)
324
325 for vec, label in zip([bv1, bv2, bv3], ["a'", "b'", "c'"]):
326 ax.text(*vec, label, fontsize=12, color='green')
327
328 # 軸や背景の調整
329 ax.set_xticks([]); ax.set_yticks([]); ax.set_zticks([])
330 ax.set_xticklabels([]); ax.set_yticklabels([]); ax.set_zticklabels([])
331 ax.set_xlabel(''); ax.set_ylabel(''); ax.set_zlabel('')
332 ax.grid(False)
333 ax.xaxis.pane.set_visible(False)
334 ax.yaxis.pane.set_visible(False)
335 ax.zaxis.pane.set_visible(False)
336 ax.xaxis.line.set_color((0, 0, 0, 0))
337 ax.yaxis.line.set_color((0, 0, 0, 0))
338 ax.zaxis.line.set_color((0, 0, 0, 0))
339 ax.set_facecolor((1, 1, 1, 0))
340
341 all_points = np.vstack([corners] + ([real_points] if lattice_points else []))
342 set_equal_aspect(ax, all_points)
343
344 plt.tight_layout()
345 plt.savefig("bravais_cell_with_basis_vectors.png",
346 dpi=300, bbox_inches='tight', transparent=True)
347 plt.show()
348
349
350def main():
351 """
352 プログラムのエントリポイント。コマンドライン引数を解析し、ブラベー単位格子の描画を実行します。
353
354 コマンドライン引数に基づいて、描画する結晶系と表示オプション(格子ベクトル、補助線、基本格子)を
355 設定し、`draw_unit_cell_with_lattice`関数を呼び出して単位格子を描画します。
356 引数が指定されない場合は、デフォルト値が使用されます。
357
358 使用方法:
359 python draw_bravais_lattice.py [cell_type] [draw_lattice_vectors] [draw_support_lines] [draw_primitive_cell]
360
361 - cell_type (str): 描画する結晶系 ('SC', 'ST', 'SO', 'SR', 'SH', 'FC', 'FO', 'BC', 'BT', 'BO', 'CO', 'SM', 'STri', 'CM'など)。
362 - draw_lattice_vectors (int): 格子ベクトルを描画するか (1: はい, 0: いいえ)。
363 - draw_support_lines (int): 補助線を描画するか (1: はい, 0: いいえ)。
364 - draw_primitive_cell (int): 基本格子を描画するか (1: はい, 0: いいえ)。
365
366 :returns: なし
367 """
368 global cell, draw_lattice_vectors, draw_support_lines, draw_primitive_cell
369
370 argv = sys.argv
371 nargs = len(argv)
372 if nargs > 1:
373 cell = argv[1]
374 if nargs > 2:
375 draw_lattice_vectors = int(argv[2])
376 if nargs > 3:
377 draw_support_lines = int(argv[3])
378 if nargs > 4:
379 draw_primitive_cell = int(argv[4])
380
381 print()
382 print(f"{cell=}")
383 print(f"{draw_lattice_vectors=}")
384 print(f"{draw_support_lines=}")
385 print(f"{draw_primitive_cell=}")
386
387 if cell[0] == 'S': # Simple (単純)
388 if cell[1] == 'C': # Simple Cubic (単純立方)
389 a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 90.0, 90.0, 90.0
390 elif cell[1:] == 'T': # Simple Tetragonal (単純正方)
391 a, b, c, alpha, beta, gamma = 5.0, 5.0, 7.0, 90.0, 90.0, 90.0
392 elif cell[1] == 'O': # Simple Orthorhombic (単純斜方)
393 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
394 elif cell[1] == 'H': # Simple Hexagonal (単純六方)
395 a, b, c, alpha, beta, gamma = 7.0, 7.0, 5.0, 90.0, 90.0, 120.0
396 elif cell[1] == 'R': # Simple Rhombohedral (単純菱面体)
397 a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 70.0, 70.0, 70.0
398 elif cell[1] == 'M': # Simple Monoclinic (単純単斜)
399 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 110.0, 90.0
400 elif cell[1:] == 'Tri': # Simple Triclinic (単純三斜)
401 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 80.0, 110.0, 60.0
402
403 lattice_points = [
404 [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
405 [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
406 ]
407
408 dashed_lines = []
409 basis_vectors = []
410
411 elif cell[0] == 'F': # Face-centered (面心)
412 if cell[1] == 'C': # Face-centered Cubic (面心立方)
413 a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 90.0, 90.0, 90.0
414 elif cell[1] == 'O': # Face-centered Orthorhombic (面心斜方)
415 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
416
417 lattice_points = [
418 [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
419 [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
420 [0.0, 0.5, 0.5],
421 [0.5, 0.0, 0.5],
422 [0.5, 0.5, 0.0],
423 [1.0, 0.5, 0.5],
424 [0.5, 1.0, 0.5],
425 [0.5, 0.5, 1.0],
426 ]
427
428 dashed_lines = [
429 [(0, 0, 0), (1, 1, 0)],
430 [(0, 1, 0), (1, 0, 0)],
431 [(0, 0, 1), (1, 1, 1)],
432 [(0, 1, 1), (1, 0, 1)],
433 [(0, 0, 0), (1, 0, 1)],
434 [(0, 0, 1), (1, 0, 0)],
435 [(0, 1, 0), (1, 1, 1)],
436 [(0, 1, 1), (1, 1, 0)],
437 [(0, 0, 0), (0, 1, 1)],
438 [(0, 1, 0), (0, 0, 1)],
439 [(1, 0, 0), (1, 1, 1)],
440 [(1, 1, 0), (1, 0, 1)],
441 ]
442
443 basis_vectors = [
444 [-0.5, 0.5, 0.5],
445 [ 0.5, -0.5, 0.5],
446 [ 0.5, 0.5, -0.5]
447 ]
448
449 elif cell[0] == 'B': # Body-centered (体心)
450 if cell[1] == 'C': # Body-centered Cubic (体心立方)
451 a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 90.0, 90.0, 90.0
452 elif cell[1] == 'T': # Body-centered Tetragonal (体心正方)
453 a, b, c, alpha, beta, gamma = 5.0, 5.0, 7.0, 90.0, 90.0, 90.0
454 elif cell[1] == 'O': # Body-centered Orthorhombic (体心斜方)
455 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
456
457 lattice_points = [
458 [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
459 [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
460 [0.5, 0.5, 0.5]
461 ]
462
463 dashed_lines = [
464 [(0, 0, 0), (1, 1, 1)],
465 [(1, 0, 0), (0, 1, 1)],
466 [(0, 1, 0), (1, 0, 1)],
467 [(0, 0, 1), (1, 1, 0)],
468 [(0.5, 0.5, 0.5), (0, 0, 0)],
469 [(0.5, 0.5, 0.5), (1, 1, 1)]
470 ]
471
472 basis_vectors = [
473 [-0.5, 0.5, 0.5],
474 [ 0.5, -0.5, 0.5],
475 [ 0.5, 0.5, -0.5]
476 ]
477
478 elif cell[0] == 'C': # Base-centered (底心)
479 if cell[1] == 'O': # Base-centered Orthorhombic (底心斜方)
480 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
481 lattice_points = [
482 [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
483 [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
484 [0.5, 0.5, 0.0],
485 [0.5, 0.5, 1.0]
486 ]
487 dashed_lines = [
488 [(0, 0, 0), (1, 1, 0)],
489 [(0, 1, 0), (1, 0, 0)],
490 [(0, 0, 1), (1, 1, 1)],
491 [(0, 1, 1), (1, 0, 1)],
492 ]
493 basis_vectors = [
494 [-0.5, 0.5, 0.5],
495 [ 0.5, -0.5, 0.5],
496 [ 0.5, 0.5, -0.5]
497 ]
498 elif cell[1] == 'M': # Base-centered Monoclinic (底心単斜)
499 a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 110.0, 90.0
500 lattice_points = [
501 [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
502 [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
503 [0.5, 0.0, 0.5],
504 [0.5, 1.0, 0.5]
505 ]
506 dashed_lines = [
507 [(0, 0, 0), (1, 0, 1)],
508 [(0, 0, 1), (1, 0, 0)],
509 [(0, 1, 0), (1, 1, 1)],
510 [(0, 1, 1), (1, 1, 0)],
511 ]
512 basis_vectors = [
513 [-0.5, 0.5, 0.5],
514 [ 0.5, -0.5, 0.5],
515 [ 0.5, 0.5, -0.5]
516 ]
517
518 else:
519 print(f"\nError: Invalid cell type [{cell}]\n")
520 return
521
522 draw_unit_cell_with_lattice(
523 a=a, b=b, c=c, alpha=alpha, beta=beta, gamma=gamma,
524 lattice_points=lattice_points,
525 dashed_lines=dashed_lines,
526 basis_vectors=basis_vectors
527 )
528
529
530if __name__ == "__main__":
531 main()