xrd_fit_xrayutilities.py ダウンロード/コピー
xrd_fit_xrayutilities.py をダウンロード
xrd_fit_xrayutilities.py
xrd_fit_xrayutilities.py
1r"""
2xrd_fit.py: X線回折(XRD)シミュレーションおよびフィッティングツール
3
4このモジュールは、xrayutilitiesライブラリを使用して動的理論に基づいたXRDシミュレーションを実行し、
5実験データに対して構造パラメータ(組成、緩和度、膜厚)を最適化します。
6また、フリンジ解析による膜厚の自動推定機能も備えています。
7
8関連リンク: :doc:`xrd_fit_usage`
9"""
10
11import argparse
12import csv
13import os
14import random
15import sys
16import traceback
17
18import numpy as np
19import matplotlib.pyplot as plt
20import xrayutilities as xu
21from scipy.optimize import minimize
22from scipy.signal import savgol_filter, find_peaks
23
24
25# ============================================================
26# 定数 / デフォルト値
27# ============================================================
28DEFAULT_MODE = "sim"
29DEFAULT_METHOD = "pso"
30DEFAULT_INFILE = ""
31DEFAULT_FIX = ""
32
33DEFAULT_SUBSTRATE_FILE = "GaN.cif"
34DEFAULT_FILM_FILE = "AlN.cif"
35
36DEFAULT_NMAXITER = 1000
37DEFAULT_TOL = 1e-7
38DEFAULT_YSCALE = "log"
39DEFAULT_RESIDUAL_SCALE = "log"
40
41TARGET_X = 0.2
42TARGET_RELAX = 0.0
43TARGET_THICK = 1500.0
44
45TRY_X = 0.0
46TRY_RELAX = 1.0
47TRY_THICK = 1500.0
48
49TWOTHETA_MIN = 32.0
50TWOTHETA_MAX = 38.0
51NPOINTS = 200
52
53RESOLUTION_WIDTH = 0.0001
54HKL = (0, 0, 2)
55
56LIMIT = 0.01
57MAX_STALL = 5000
58
59NINTERVAL_SAVE = 100
60NINTERVAL_PRINT = 10
61NINTERVAL_PLOT = 10
62
63XRANGE = 1.0
64RRANGE = 1.0
65TRANGE = 10.0
66
67PARAMETER_NAMES = ["x", "relax", "thick"]
68DEFAULT_PRIMARY_SET = "best0"
69DEFAULT_PSO_PREFIX = "pso"
70
71DEFAULT_GUESS_LOW_DEG = 16.0
72DEFAULT_GUESS_HIGH_DEG = 17.3
73DEFAULT_NSMOOTH_POINTS = 31
74DEFAULT_NSMOOTH_ORDER = 3
75DEFAULT_NQ_UNIFORM = 4096
76DEFAULT_NGUESS_KEEP = 5
77
78DEFAULT_PSO_NPARTICLES = 12
79DEFAULT_PSO_W = 0.72
80DEFAULT_PSO_C1 = 1.49
81DEFAULT_PSO_C2 = 1.49
82
83DEFAULT_CLUSTER_GAP_FACTOR = 1.6
84
85
86# ============================================================
87# argparse
88# ============================================================
89def build_parser():
90 """
91 コマンドライン引数を解析するためのパーサーを構築します。
92
93 詳細説明:
94 この関数はargparseモジュールを使用して、XRDシミュレーション、フィッティング、
95 および膜厚推定ツールが受け入れるコマンドライン引数を定義します。
96 各引数にはデフォルト値、選択肢、ヘルプメッセージが含まれます。
97
98 Returns:
99 :returns: argparse.ArgumentParser: 設定済みのパーサーオブジェクト。
100 """
101 parser = argparse.ArgumentParser(
102 description="動的理論に基づくXRDシミュレーション、フィッティング、および膜厚推定"
103 )
104 parser.add_argument(
105 "--mode",
106 default=DEFAULT_MODE,
107 choices=["sim", "read", "fit", "guess"],
108 help="実行モード: sim (シミュレーション), read (読込), fit (最適化), guess (膜厚推定)",
109 )
110 parser.add_argument(
111 "--method",
112 default=DEFAULT_METHOD,
113 choices=["random", "nelder-mead", "bfgs", "cg", "pso"],
114 help="最適化手法。fitモード時に有効",
115 )
116 parser.add_argument(
117 "--infile",
118 default=DEFAULT_INFILE,
119 help='2theta-intensityデータを含むテキストファイルへのパス',
120 )
121 parser.add_argument(
122 "--substrate_file",
123 default=DEFAULT_SUBSTRATE_FILE,
124 help=f'基板のCIFファイルパス (デフォルト: {DEFAULT_SUBSTRATE_FILE})',
125 )
126 parser.add_argument(
127 "--film_file",
128 default=DEFAULT_FILM_FILE,
129 help=f'膜のCIFファイルパス (デフォルト: {DEFAULT_FILM_FILE})',
130 )
131 parser.add_argument(
132 "--fix",
133 default=DEFAULT_FIX,
134 help='固定するパラメータ(カンマ区切り、例: "x,relax")',
135 )
136 parser.add_argument(
137 "--nmaxiter",
138 type=int,
139 default=DEFAULT_NMAXITER,
140 help=f"最大反復回数 (デフォルト: {DEFAULT_NMAXITER})",
141 )
142 parser.add_argument(
143 "--tol",
144 type=float,
145 default=DEFAULT_TOL,
146 help=f"収束判定の許容誤差 (デフォルト: {DEFAULT_TOL})",
147 )
148 parser.add_argument(
149 "--yscale",
150 default=DEFAULT_YSCALE,
151 choices=["linear", "log"],
152 help=f'プロットのY軸スケール (デフォルト: "{DEFAULT_YSCALE}")',
153 )
154 parser.add_argument(
155 "--residual_scale",
156 default=DEFAULT_RESIDUAL_SCALE,
157 choices=["linear", "log"],
158 help=f'残差計算に使用するスケール (デフォルト: "{DEFAULT_RESIDUAL_SCALE}")',
159 )
160 parser.add_argument(
161 "--guess_low",
162 type=float,
163 default=DEFAULT_GUESS_LOW_DEG,
164 help="guessモードでの解析開始角度 [deg]",
165 )
166 parser.add_argument(
167 "--guess_high",
168 type=float,
169 default=DEFAULT_GUESS_HIGH_DEG,
170 help="guessモードでの解析終了角度 [deg]",
171 )
172 parser.add_argument(
173 "--nsmooth_points",
174 type=int,
175 default=DEFAULT_NSMOOTH_POINTS,
176 help="guessモードでの平滑化ウィンドウ点数",
177 )
178 parser.add_argument(
179 "--nsmooth_order",
180 type=int,
181 default=DEFAULT_NSMOOTH_ORDER,
182 help="guessモードでの平滑化多項式次数",
183 )
184 parser.add_argument(
185 "--nguess_keep",
186 type=int,
187 default=DEFAULT_NGUESS_KEEP,
188 help="guessモードで保持する膜厚候補の数",
189 )
190 parser.add_argument(
191 "--cluster_gap_factor",
192 type=float,
193 default=DEFAULT_CLUSTER_GAP_FACTOR,
194 help=f"フリンジクラスター判定用のギャップ係数 (デフォルト: {DEFAULT_CLUSTER_GAP_FACTOR})",
195 )
196 parser.add_argument(
197 "--pso_nparticles",
198 type=int,
199 default=DEFAULT_PSO_NPARTICLES,
200 help=f"PSOの粒子数 (デフォルト: {DEFAULT_PSO_NPARTICLES})",
201 )
202 parser.add_argument(
203 "--pso_w",
204 type=float,
205 default=DEFAULT_PSO_W,
206 help=f"PSOの慣性重み (デフォルト: {DEFAULT_PSO_W})",
207 )
208 parser.add_argument(
209 "--pso_c1",
210 type=float,
211 default=DEFAULT_PSO_C1,
212 help=f"PSOの自己学習係数 (デフォルト: {DEFAULT_PSO_C1})",
213 )
214 parser.add_argument(
215 "--pso_c2",
216 type=float,
217 default=DEFAULT_PSO_C2,
218 help=f"PSOの社会学習係数 (デフォルト: {DEFAULT_PSO_C2})",
219 )
220 parser.add_argument(
221 "--pso_stall_max",
222 type=int,
223 default=15,
224 help="改善がない場合にPSOを停止する最大反復数",
225 )
226 parser.add_argument(
227 "--pso_spread_rtol",
228 type=float,
229 default=0.10,
230 help="粒子の分散に基づく停止条件の相対許容誤差",
231 )
232 return parser
233
234
235def initialize(parser):
236 """
237 コマンドライン引数を解析し、結果を返します。
238
239 Parameters:
240 :param parser: argparse.ArgumentParser: argparse.ArgumentParserオブジェクト。
241 Returns:
242 :returns: argparse.Namespace: 解析された引数を含むオブジェクト。
243 """
244 return parser.parse_args()
245
246
247# ============================================================
248# ファイル名ヘルパー
249# ============================================================
250def get_parameter_filename(infile):
251 """
252 入力ファイル名に基づいてパラメータ保存用のCSVファイル名を生成します。
253
254 詳細説明:
255 入力ファイル名が存在する場合はそのファイル名から拡張子を除いた名前に "-parameters.csv" を追加します。
256 入力ファイル名が空の場合は "parameters.csv" を返します。
257
258 Parameters:
259 :param infile: str: 入力データファイル名。
260
261 Returns:
262 :returns: str: パラメータCSVファイル名。
263 """
264 if infile == "":
265 return "parameters.csv"
266 filebody, _ = os.path.splitext(infile)
267 return f"{filebody}-parameters.csv"
268
269
270def get_guess_peaks_filename(infile):
271 """
272 入力ファイル名に基づいて膜厚推定モードで検出したピークの保存用CSVファイル名を生成します。
273
274 詳細説明:
275 入力ファイル名が存在する場合はそのファイル名から拡張子を除いた名前に "-guess-peaks.csv" を追加します。
276 入力ファイル名が空の場合は "guess-peaks.csv" を返します。
277
278 Parameters:
279 :param infile: str: 入力データファイル名。
280
281 Returns:
282 :returns: str: ピークデータCSVファイル名。
283 """
284 if infile == "":
285 return "guess-peaks.csv"
286 filebody, _ = os.path.splitext(infile)
287 return f"{filebody}-guess-peaks.csv"
288
289
290def get_guess_clusters_filename(infile):
291 """
292 入力ファイル名に基づいて膜厚推定モードで解析したクラスターの保存用CSVファイル名を生成します。
293
294 詳細説明:
295 入力ファイル名が存在する場合はそのファイル名から拡張子を除いた名前に "-guess-clusters.csv" を追加します。
296 入力ファイル名が空の場合は "guess-clusters.csv" を返します。
297
298 Parameters:
299 :param infile: str: 入力データファイル名。
300
301 Returns:
302 :returns: str: クラスターデータCSVファイル名。
303 """
304 if infile == "":
305 return "guess-clusters.csv"
306 filebody, _ = os.path.splitext(infile)
307 return f"{filebody}-guess-clusters.csv"
308
309
310def parse_fix_list(fix_str):
311 """
312 固定パラメータの文字列をリストに変換します。
313
314 詳細説明:
315 カンマ区切りの文字列を受け取り、各パラメータ名を小文字に変換し、
316 空白を除去したリストとして返します。空の文字列は空のリストを返します。
317
318 Parameters:
319 :param fix_str: str: カンマ区切りのパラメータ名文字列(例: "x,relax")。
320
321 Returns:
322 :returns: list[str]: 小文字に統一された固定するパラメータ名のリスト。
323 """
324 if fix_str.strip() == "":
325 return []
326 return [s.strip().lower() for s in fix_str.split(",") if s.strip() != ""]
327
328
329# ============================================================
330# パラメータユーティリティ
331# ============================================================
332def make_default_parameter_set():
333 """
334 単一のパラメータセット(組成、緩和度、膜厚)の初期辞書を作成します。
335
336 詳細説明:
337 各パラメータには 'value' (現在の値) と 'optid' (最適化対象フラグ: 1=対象, 0=固定) が含まれます。
338 初期値は定数 `TRY_X`, `TRY_RELAX`, `TRY_THICK` から取得され、
339 全て最適化対象として設定されます。
340
341 Returns:
342 :returns: dict[str, dict[str, float or int]]: 初期値と最適化フラグを含むパラメータ辞書。
343 例: `{"x": {"value": 0.0, "optid": 1}, ...}`
344 """
345 return {
346 "x": {"value": TRY_X, "optid": 1},
347 "relax": {"value": TRY_RELAX, "optid": 1},
348 "thick": {"value": TRY_THICK, "optid": 1},
349 }
350
351
352def clone_parameter_set(param_set):
353 """
354 指定されたパラメータセットを深くコピーします。
355
356 詳細説明:
357 元のパラメータセットの内容を変更せずに新しい独立した辞書を作成するために使用されます。
358 これにより、最適化プロセス中にパラメータが不意に変更されるのを防ぎます。
359
360 Parameters:
361 :param param_set: dict: コピーするパラメータセット。
362
363 Returns:
364 :returns: dict: コピーされたパラメータセット。
365 """
366 out = {}
367 for name in param_set:
368 out[name] = {
369 "value": float(param_set[name]["value"]),
370 "optid": int(param_set[name]["optid"]),
371 }
372 return out
373
374
375def make_default_parameter_sets():
376 """
377 デフォルトのプライマリパラメータセットを含む、複数のパラメータセットを管理する辞書を作成します。
378
379 詳細説明:
380 `DEFAULT_PRIMARY_SET` (通常 'best0') というキーで、
381 `make_default_parameter_set()` で生成された初期パラメータセットを保持します。
382 これは、複数の最適化粒子や候補を管理する際に基盤となる構造を提供します。
383
384 Returns:
385 :returns: dict[str, dict]: デフォルトのプライマリセットを含むパラメータ辞書全体の構造。
386 """
387 return {DEFAULT_PRIMARY_SET: make_default_parameter_set()}
388
389
390def apply_fix_list(param_sets, fix_list):
391 """
392 指定されたパラメータの最適化フラグをオフ(0)に設定します。
393
394 詳細説明:
395 `param_sets` 内の全てのパラメータセットに対して、`fix_list` に含まれるパラメータの
396 `optid` (最適化対象フラグ) を0に設定し、最適化から除外します。
397
398 Parameters:
399 :param param_sets: dict: 変更対象のパラメータ辞書 (複数のセットを含む)。
400 :param fix_list: list[str]: 固定するパラメータ名のリスト。
401 """
402 for set_name in param_sets:
403 for varname in fix_list:
404 if varname in param_sets[set_name]:
405 param_sets[set_name][varname]["optid"] = 0
406
407
408def read_parameters(parameter_file):
409 """
410 CSVファイルからパラメータ設定を読み込みます。ファイルが存在しない場合はデフォルト値を返します。
411
412 詳細説明:
413 指定されたCSVファイルからパラメータ名、値、最適化IDを読み込み、
414 `make_default_parameter_sets` の形式に沿った辞書構造を構築します。
415 CSVファイルのヘッダーが不正な場合や、ファイルが空の場合は、警告を出力し
416 デフォルトのパラメータセットを返します。
417
418 Parameters:
419 :param parameter_file: str: パラメータCSVファイルへのパス。
420
421 Returns:
422 :returns: dict[str, dict]: 読み込まれたパラメータセットの辞書。
423 ファイルが存在しない場合や無効な場合は、デフォルトのパラメータセットが返されます。
424 """
425 param_sets = make_default_parameter_sets()
426
427 if not os.path.isfile(parameter_file):
428 return param_sets
429
430 rows = []
431 with open(parameter_file, "r", newline="", encoding="utf-8") as f:
432 reader = csv.reader(f)
433 for row in reader:
434 rows.append(row)
435
436 if len(rows) == 0:
437 return param_sets
438
439 header = rows[0]
440 if len(header) < 3 or header[0] != "varname" or header[1] != "value" or header[2] != "optid":
441 print(f"Warning: invalid parameter CSV header in {parameter_file}. Use defaults.")
442 return param_sets
443
444 loaded = {}
445 for row in rows[1:]:
446 if len(row) < 3:
447 continue
448
449 varname_raw = row[0].strip()
450 value_str = row[1].strip()
451 optid_str = row[2].strip()
452
453 if varname_raw == "":
454 continue
455
456 if ":" in varname_raw:
457 set_name, varname = varname_raw.split(":", 1)
458 set_name = set_name.strip()
459 varname = varname.strip()
460 else:
461 set_name = DEFAULT_PRIMARY_SET
462 varname = varname_raw
463
464 if set_name == "" or varname == "":
465 continue
466 if varname not in PARAMETER_NAMES:
467 continue
468
469 try:
470 value = float(value_str)
471 optid = int(optid_str)
472 except ValueError:
473 continue
474
475 if set_name not in loaded:
476 loaded[set_name] = make_default_parameter_set()
477
478 loaded[set_name][varname]["value"] = value
479 loaded[set_name][varname]["optid"] = 1 if optid != 0 else 0
480
481 if len(loaded) > 0:
482 param_sets = loaded
483
484 if DEFAULT_PRIMARY_SET not in param_sets:
485 param_sets[DEFAULT_PRIMARY_SET] = make_default_parameter_set()
486
487 return param_sets
488
489
490def save_parameters(parameter_file, param_sets):
491 """
492 現在のパラメータセットをCSVファイルに書き出します。
493
494 詳細説明:
495 `param_sets` 辞書に格納されている全てのパラメータ(複数のセットを含む)を、
496 "varname", "value", "optid" のヘッダーを持つCSV形式で指定されたファイルに保存します。
497 各パラメータ名は `set_name:varname` の形式で保存されます。
498
499 Parameters:
500 :param parameter_file: str: パラメータを保存するファイルパス。
501 :param param_sets: dict[str, dict]: 保存するパラメータデータ。
502 """
503 rows = [["varname", "value", "optid"]]
504
505 set_names = sorted(param_sets.keys())
506 for set_name in set_names:
507 for varname in PARAMETER_NAMES:
508 entry = param_sets[set_name][varname]
509 rows.append(
510 [
511 f"{set_name}:{varname}",
512 f"{entry['value']:.16g}",
513 str(int(entry["optid"])),
514 ]
515 )
516
517 with open(parameter_file, "w", newline="", encoding="utf-8") as f:
518 writer = csv.writer(f)
519 writer.writerows(rows)
520
521
522def save_guess_peak_csv(outfile, peak_rows):
523 """
524 膜厚推定モードで検出したピーク情報をCSVファイルに保存します。
525
526 Parameters:
527 :param outfile: str: 保存先ファイルパス。
528 :param peak_rows: list[dict]: ピーク情報を含む辞書のリスト。
529 各辞書は `cluster_id`, `n`, `q`, `twotheta`, `used_stage1`, `used_stage2`,
530 `resid_stage1`, `resid_stage2` のキーを持つと期待されます。
531 """
532 rows = [["cluster_id", "n", "q", "2theta", "used_stage1", "used_stage2", "resid_stage1", "resid_stage2"]]
533 for row in peak_rows:
534 rows.append(
535 [
536 str(row["cluster_id"]),
537 str(row["n"]),
538 f"{row['q']:.16g}",
539 f"{row['twotheta']:.16g}",
540 str(int(row["used_stage1"])),
541 str(int(row["used_stage2"])),
542 f"{row['resid_stage1']:.16g}",
543 f"{row['resid_stage2']:.16g}",
544 ]
545 )
546
547 with open(outfile, "w", newline="", encoding="utf-8") as f:
548 writer = csv.writer(f)
549 writer.writerows(rows)
550
551
552def save_guess_cluster_csv(outfile, cluster_rows):
553 """
554 膜厚推定モードで解析したクラスター(膜厚候補)情報をCSVファイルに保存します。
555
556 Parameters:
557 :param outfile: str: 保存先ファイルパス。
558 :param cluster_rows: list[dict]: クラスター情報を含む辞書のリスト。
559 各辞書は `cluster_id`, `npeaks`, `dq_est`, `thick_fft`, `thick_reg`, `confidence`
560 のキーを持つと期待されます。
561 """
562 rows = [["cluster_id", "npeaks", "dq_est", "thick_fft", "thick_reg", "confidence"]]
563 for row in cluster_rows:
564 rows.append(
565 [
566 str(row["cluster_id"]),
567 str(row["npeaks"]),
568 f"{row['dq_est']:.16g}",
569 f"{row['thick_fft']:.16g}",
570 f"{row['thick_reg']:.16g}",
571 f"{row['confidence']:.16g}",
572 ]
573 )
574 with open(outfile, "w", newline="", encoding="utf-8") as f:
575 writer = csv.writer(f)
576 writer.writerows(rows)
577
578
579def parameter_set_to_values(param_set):
580 """
581 パラメータセット辞書から組成(x)、緩和度(relax)、膜厚(thick)のタプルを抽出します。
582
583 Parameters:
584 :param param_set: dict: パラメータセット辞書。
585
586 Returns:
587 :returns: tuple[float, float, float]: (x, relax, thick) の値のタプル。
588 """
589 return (
590 float(param_set["x"]["value"]),
591 float(param_set["relax"]["value"]),
592 float(param_set["thick"]["value"]),
593 )
594
595
596def get_optimize_names(param_set):
597 """
598 指定されたパラメータセット内で最適化対象(optid=1)となっているパラメータ名のリストを取得します。
599
600 Parameters:
601 :param param_set: dict: パラメータセット辞書。
602
603 Returns:
604 :returns: list[str]: 最適化対象のパラメータ名("x", "relax", "thick")のリスト。
605 """
606 names = []
607 for name in PARAMETER_NAMES:
608 if int(param_set[name]["optid"]) != 0:
609 names.append(name)
610 return names
611
612
613def pack_optimize_values(param_set, optimize_names):
614 """
615 最適化対象のパラメータの値をNumPy配列にパックします。
616
617 Parameters:
618 :param param_set: dict: 基準となるパラメータセット。
619 :param optimize_names: list[str]: 最適化対象のパラメータ名のリスト。
620
621 Returns:
622 :returns: numpy.ndarray: 最適化対象のパラメータ値を含むNumPy配列。
623 """
624 return np.array([param_set[name]["value"] for name in optimize_names], dtype=float)
625
626
627def unpack_optimize_values(base_param_set, optimize_names, xvec):
628 """
629 NumPy配列の値を、指定されたパラメータ名の順序に従って元のパラメータ辞書形式に戻します。
630
631 詳細説明:
632 `base_param_set` をクローンし、`optimize_names` の順序で `xvec` の値で更新します。
633 これにより、最適化ルーチンから返された値をパラメータセットに適用できます。
634
635 Parameters:
636 :param base_param_set: dict: 基準となるパラメータセット(最適化対象外の値を保持するために使用)。
637 :param optimize_names: list[str]: 最適化対象のパラメータ名のリスト。
638 :param xvec: numpy.ndarray: 最適化によって得られたパラメータ値の配列。
639
640 Returns:
641 :returns: dict: 更新されたパラメータセット。
642 """
643 work = clone_parameter_set(base_param_set)
644 for i, name in enumerate(optimize_names):
645 work[name]["value"] = float(xvec[i])
646 return work
647
648
649def set_best_parameter_set(param_sets, param_set):
650 """
651 現在の最良のパラメータセットを `param_sets` 辞書内の `DEFAULT_PRIMARY_SET` キーに保存します。
652
653 詳細説明:
654 これにより、最適化の進捗に合わせて最良の解が常に追跡され、
655 プログラムの実行終了時や中断時に最新の最良パラメータが保存されます。
656
657 Parameters:
658 :param param_sets: dict: 複数のパラメータセットを管理する辞書。
659 :param param_set: dict: 最良とみなされる単一のパラメータセット。
660 """
661 param_sets[DEFAULT_PRIMARY_SET] = clone_parameter_set(param_set)
662
663
664def replace_pso_parameter_sets(param_sets, candidate_sets):
665 """
666 PSOモード用の粒子パラメータセットを更新します。
667
668 詳細説明:
669 `param_sets` からPSO関連の既存のパラメータセットを削除し、
670 `candidate_sets` から新しいPSO粒子パラメータセットを追加します。
671 PSOのプレフィックスを持たないセットは保持されます。
672
673 Parameters:
674 :param param_sets: dict: 現在の全てのパラメータセット。
675 :param candidate_sets: list[dict]: PSOの新しい粒子パラメータセットのリスト。
676
677 Returns:
678 :returns: dict: PSO粒子パラメータが更新された新しいパラメータセット辞書。
679 """
680 keep_names = [name for name in param_sets.keys() if not name.startswith(DEFAULT_PSO_PREFIX)]
681 new_sets = {}
682 for name in keep_names:
683 new_sets[name] = clone_parameter_set(param_sets[name])
684
685 for i, param_set in enumerate(candidate_sets):
686 set_name = f"{DEFAULT_PSO_PREFIX}{i}"
687 new_sets[set_name] = clone_parameter_set(param_set)
688
689 if DEFAULT_PRIMARY_SET not in new_sets:
690 new_sets[DEFAULT_PRIMARY_SET] = make_default_parameter_set()
691
692 return new_sets
693
694
695def get_pso_parameter_sets(param_sets):
696 """
697 `DEFAULT_PSO_PREFIX` (通常 "pso") で始まるキーを持つパラメータセットを全て取得します。
698
699 詳細説明:
700 これはPSOアルゴリズムで使用される各粒子の位置を初期化または取得するために使用されます。
701 取得されたセットは、元の辞書から独立したコピーとして提供されます。
702
703 Parameters:
704 :param param_sets: dict: 複数のパラメータセットを管理する辞書。
705
706 Returns:
707 :returns: list[dict]: PSO粒子として使用されるパラメータセットのリスト。
708 """
709 names = sorted([name for name in param_sets.keys() if name.startswith(DEFAULT_PSO_PREFIX)])
710 return [clone_parameter_set(param_sets[name]) for name in names]
711
712
713# ============================================================
714# 材料 / モデル準備
715# ============================================================
716def prepare_materials(substrate_file, film_file):
717 """
718 CIFファイルから基板と膜の材料オブジェクトを準備し、弾性定数を設定します。
719
720 詳細説明:
721 指定されたCIFファイルパスからX-rayUtilitiesの `Crystal` オブジェクトをロードします。
722 材料がHexagonal対称性を持つことを確認し、もし弾性テンソルが定義されていない場合は
723 デフォルトの六方晶系弾性定数を割り当てて、歪み計算が続行できるようにします。
724 ファイルが見つからない場合や読み込みエラーが発生した場合は、プログラムを終了します。
725
726 Parameters:
727 :param substrate_file: str: 基板のCIFファイルパス。
728 :param film_file: str: 膜のCIFファイルパス。
729
730 Returns:
731 :returns: tuple[xrayutilities.materials.Crystal, xrayutilities.materials.Crystal]:
732 (substrate, film) の材料オブジェクト。
733 """
734 if not os.path.isfile(substrate_file):
735 print(f"Error: substrate_file not found : {substrate_file}")
736 sys.exit(1)
737
738 if not os.path.isfile(film_file):
739 print(f"Error: film_file not found : {film_file}")
740 sys.exit(1)
741
742 try:
743 substrate = xu.materials.Crystal.fromCIF(substrate_file)
744 except Exception as e:
745 print(f"Error reading substrate CIF : {substrate_file}")
746 print(e)
747 sys.exit(1)
748
749 try:
750 film = xu.materials.Crystal.fromCIF(film_file)
751 except Exception as e:
752 print(f"Error reading film CIF : {film_file}")
753 print(e)
754 sys.exit(1)
755
756 substrate.symm_class_name = "Hexagonal"
757 film.symm_class_name = "Hexagonal"
758
759 def ensure_elastic(material, name, default_cij):
760 """
761 材料オブジェクトに弾性定数が定義されていることを確認し、
762 存在しない場合はデフォルト値を割り当てます。
763
764 Parameters:
765 :param material: xrayutilities.materials.Crystal: 弾性定数をチェックする材料オブジェクト。
766 :param name: str: 材料の名前(警告メッセージ用)。
767 :param default_cij: tuple[float, float, float, float, float]: デフォルトの六方晶系弾性定数 (C11, C12, C13, C33, C44) のタプル。
768 """
769 has_elastic = True
770 try:
771 material.elastic
772 except Exception:
773 has_elastic = False
774
775 try:
776 material.cijkl
777 except Exception:
778 has_elastic = False
779
780 if has_elastic:
781 return
782
783 print()
784 print(f"Warning: elastic tensor not defined for {name}")
785 print("Strain calculation may be inaccurate.")
786 print("Default hexagonal elastic constants will be used and calculation continues.")
787 print()
788
789 c11, c12, c13, c33, c44 = default_cij
790 cij = xu.materials.material.HexagonalElasticTensor(c11, c12, c13, c33, c44)
791 material.elastic = cij
792 material.cijkl = xu.materials.material.Cij2Cijkl(cij)
793
794 ensure_elastic(substrate, "substrate", (390, 145, 106, 398, 105))
795 ensure_elastic(film, "film", (396, 137, 108, 373, 116))
796
797 return substrate, film
798
799
800def make_scan_axis():
801 """
802 XRDシミュレーション用のデフォルトの角度軸 (2theta, omega) を生成します。
803
804 詳細説明:
805 `TWOTHETA_MIN` から `TWOTHETA_MAX` の範囲で `NPOINTS` の数の2theta角度を生成し、
806 それに対応するomega角度 (2thetaの半分) を計算します。
807
808 Returns:
809 :returns: tuple[numpy.ndarray, numpy.ndarray]: (2theta角度の配列 [deg], omega角度の配列 [deg])。
810 """
811 twotheta = np.linspace(TWOTHETA_MIN, TWOTHETA_MAX, NPOINTS)
812 omega = twotheta / 2.0
813 return twotheta, omega
814
815
816def model(substrate, film, x, relax, thick, energy):
817 """
818 指定されたパラメータで動的XRDシミュレーションモデルを構築します。
819
820 詳細説明:
821 X-rayUtilitiesの `Layer` オブジェクトと `PseudomorphicStack001` を使用して、
822 基板と膜からなるスタックの動的理論モデルを定義します。
823 膜は基板と膜の合金であり、組成 `x`、緩和度 `relax`、膜厚 `thick` を持ちます。
824
825 Parameters:
826 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
827 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト(合金のもう一方の端成分)。
828 :param x: float: 合金の組成比 (0.0 から 1.0)。
829 :param relax: float: 膜の緩和度 (0.0 は完全擬似格子、1.0 は完全緩和)。
830 :param thick: float: 膜の厚さ [Å]。
831 :param energy: float: X線エネルギー [eV]。
832
833 Returns:
834 :returns: xrayutilities.simpack.DynamicalModel: 構築された動的理論モデル。
835 """
836 substrate_layer = xu.simpack.Layer(substrate, float("inf"))
837 alloy = xu.materials.material.Alloy(substrate, film, x)
838 film_layer = xu.simpack.Layer(alloy, thick, relaxation=relax)
839 stack = xu.simpack.PseudomorphicStack001("film/substrate", substrate_layer + film_layer)
840 dyn_model = xu.simpack.DynamicalModel(
841 stack,
842 energy=energy,
843 resolution_width=RESOLUTION_WIDTH,
844 )
845 return dyn_model
846
847
848def simulate(dyn_model, omega):
849 """
850 動的理論モデルを使用してXRD強度分布を計算します。
851
852 Parameters:
853 :param dyn_model: xrayutilities.simpack.DynamicalModel: 計算に使用する構築済みモデル。
854 :param omega: numpy.ndarray: 計算する角度範囲のomega値の配列 [deg]。
855
856 Returns:
857 :returns: numpy.ndarray: 計算されたXRD強度分布の配列。
858 """
859 intensity = dyn_model.simulate(omega, hkl=HKL)
860 return np.asarray(intensity, dtype=float)
861
862
863# ============================================================
864# データ入出力
865# ============================================================
866def read_text_data(infile):
867 """
868 テキストファイルから2列データ (2theta, intensity) を読み込みます。
869
870 詳細説明:
871 指定されたテキストファイルから数値データを読み込み、
872 1列目を2theta角度、2列目を強度として解釈します。
873 ファイルが存在しない場合や、フォーマットが不正な場合はエラーで終了します。
874 omega角度は2thetaの半分として計算されます。
875
876 Parameters:
877 :param infile: str: 2theta-intensityデータを含むテキストファイルへのパス。
878
879 Returns:
880 :returns: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]:
881 (2theta角度の配列 [deg], omega角度の配列 [deg], 強度値の配列)。
882 """
883 if not os.path.isfile(infile):
884 print(f'Error: infile "{infile}" not found.')
885 sys.exit(1)
886
887 data = np.loadtxt(infile)
888
889 if data.ndim != 2 or data.shape[1] < 2:
890 print("Error: input file must contain at least two columns: 2theta intensity")
891 sys.exit(1)
892
893 twotheta = data[:, 0]
894 intensity = data[:, 1]
895 omega = twotheta / 2.0
896 return twotheta, omega, np.asarray(intensity, dtype=float)
897
898
899def read_data(infile, substrate, film, energy):
900 """
901 XRDデータファイルを読み込むか、ファイルが指定されていない場合は模擬データを生成します。
902
903 詳細説明:
904 `infile` が空文字列の場合、`TARGET_X`, `TARGET_RELAX`, `TARGET_THICK`
905 で定義されたターゲットパラメータを使用して模擬XRDパターンを生成します。
906 `infile` が指定されている場合は、`read_text_data` を呼び出してファイルを読み込みます。
907
908 Parameters:
909 :param infile: str: 2theta-intensityデータを含むテキストファイルへのパス。
910 空の場合は模擬データを生成します。
911 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
912 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
913 :param energy: float: X線エネルギー [eV]。
914
915 Returns:
916 :returns: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]:
917 (2theta角度の配列 [deg], omega角度の配列 [deg], 強度値の配列)。
918 """
919 if infile == "":
920 twotheta, omega = make_scan_axis()
921 target_model = model(substrate, film, TARGET_X, TARGET_RELAX, TARGET_THICK, energy)
922 intensity = simulate(target_model, omega)
923 return twotheta, omega, intensity
924 return read_text_data(infile)
925
926
927# ============================================================
928# ユーティリティ
929# ============================================================
930def clamp_values(x, relax, thick):
931 """
932 組成(x)、緩和度(relax)、膜厚(thick)のパラメータを物理的に妥当な範囲にクランプします。
933
934 詳細説明:
935 - `x` は0.0から1.0の範囲に制限されます。
936 - `relax` は0.0から1.0の範囲に制限されます。
937 - `thick` は1.0以上の値に制限されます。
938
939 Parameters:
940 :param x: float: 組成比。
941 :param relax: float: 緩和度。
942 :param thick: float: 膜厚 [Å]。
943
944 Returns:
945 :returns: tuple[float, float, float]: クランプされた (x, relax, thick) の値。
946 """
947 x = min(max(x, 0.0), 1.0)
948 relax = min(max(relax, 0.0), 1.0)
949 thick = max(thick, 1.0)
950 return x, relax, thick
951
952
953def calc_residual(int_target, int_try, residual_scale="log"):
954 """
955 ターゲット強度とシミュレーション強度の残差 (L2ノルム) を計算します。
956
957 詳細説明:
958 `residual_scale` が "log" の場合、強度は対数スケールに変換されてから残差が計算されます。
959 これにより、強度の低い部分のフィッティング精度を向上させることができます。
960 "linear" の場合は線形スケールで計算されます。
961 強度がゼロになることを避けるため、非常に小さいイプシロン値 (`eps`) が使用されます。
962
963 Parameters:
964 :param int_target: numpy.ndarray: 実験データまたはターゲット強度。
965 :param int_try: numpy.ndarray: 計算データまたは試行強度。
966 :param residual_scale: str: 残差計算に使用するスケール ("log" または "linear")。
967
968 Returns:
969 :returns: float: 計算された残差の総和 (L2ノルムの和)。
970 """
971 eps = 1e-30
972
973 if residual_scale == "log":
974 y_target = np.log(np.maximum(int_target, eps))
975 y_try = np.log(np.maximum(int_try, eps))
976 else:
977 y_target = int_target
978 y_try = int_try
979
980 return np.sum((y_target - y_try) ** 2)
981
982
983def update_rrange(res):
984 """
985 残差値に基づいて、ランダム探索の探索範囲 (rrange) を動的に調整します。
986
987 詳細説明:
988 残差が大きい場合は探索範囲を広げ、小さい場合は狭めることで、
989 最適化の効率を向上させます。残差が5.0より大きい場合は0.5を返し、
990 それ以外の場合は残差の1/10を返します。
991
992 Parameters:
993 :param res: float: 現在の残差値。
994
995 Returns:
996 :returns: float: 更新された探索範囲の係数。
997 """
998 if res > 5.0:
999 return 0.5
1000 return res / 10.0
1001
1002
1003def calc_pattern_from_param_set(substrate, film, energy, omega, param_set):
1004 """
1005 パラメータセットを直接渡してシミュレーション強度を計算します。
1006
1007 詳細説明:
1008 `param_set` から組成、緩和度、膜厚を抽出し、
1009 `clamp_values` で物理的範囲にクランプした後、モデルを構築し強度をシミュレートします。
1010
1011 Parameters:
1012 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
1013 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
1014 :param energy: float: X線エネルギー [eV]。
1015 :param omega: numpy.ndarray: 計算する角度範囲のomega値の配列 [deg]。
1016 :param param_set: dict: 評価するパラメータセット。
1017
1018 Returns:
1019 :returns: numpy.ndarray: 計算されたXRD強度分布の配列。
1020 """
1021 x, relax, thick = parameter_set_to_values(param_set)
1022 x, relax, thick = clamp_values(x, relax, thick)
1023 dyn_model = model(substrate, film, x, relax, thick, energy)
1024 intensity = simulate(dyn_model, omega)
1025 return intensity
1026
1027
1028def objective_from_param_set(substrate, film, energy, omega, int_target, param_set, residual_scale="log"):
1029 """
1030 最適化に使用する目的関数を計算します。残差に加えて物理的境界外へのペナルティを含みます。
1031
1032 詳細説明:
1033 `param_set` からパラメータを抽出し、シミュレーションを実行して強度を計算します。
1034 計算された強度とターゲット強度との残差を `calc_residual` で求めます。
1035 また、組成(x)、緩和度(relax)、膜厚(thick)が物理的に妥当な範囲
1036 (x:[0,1], relax:[0,1], thick>0) を逸脱した場合、
1037 大きなペナルティが追加され、最適化がこれらの制約内で解を見つけるように誘導されます。
1038
1039 Parameters:
1040 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
1041 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
1042 :param energy: float: X線エネルギー [eV]。
1043 :param omega: numpy.ndarray: 計算する角度範囲のomega値の配列 [deg]。
1044 :param int_target: numpy.ndarray: 実験データまたはターゲット強度。
1045 :param param_set: dict: 評価するパラメータセット。
1046 :param residual_scale: str: 残差計算に使用するスケール ("log" または "linear")。
1047
1048 Returns:
1049 :returns: float: ペナルティ込みの残差の総和。
1050 """
1051 x, relax, thick = parameter_set_to_values(param_set)
1052
1053 penalty = 0.0
1054 if x < 0.0:
1055 penalty += 1e6 * (0.0 - x) ** 2
1056 if x > 1.0:
1057 penalty += 1e6 * (x - 1.0) ** 2
1058 if relax < 0.0:
1059 penalty += 1e6 * (0.0 - relax) ** 2
1060 if relax > 1.0:
1061 penalty += 1e6 * (relax - 1.0) ** 2
1062 if thick <= 0.0:
1063 penalty += 1e6 * (1.0 - thick) ** 2 # thickは1.0未満にならないようにペナルティ
1064
1065 intensity = calc_pattern_from_param_set(substrate, film, energy, omega, param_set)
1066 return calc_residual(int_target, intensity, residual_scale=residual_scale) + penalty
1067
1068
1069def plot_xrd(omega, intensity_list, labels, title, yscale="log", text_lines=None, wait=False):
1070 """
1071 複数のXRDパターンをプロットし表示します。
1072
1073 詳細説明:
1074 与えられたomega軸と複数の強度配列をプロットします。
1075 各強度配列には対応するラベルが付けられます。
1076 Y軸のスケール("linear"または"log")とプロットのタイトルを設定できます。
1077 追加のテキスト行をプロット領域に表示することも可能です。
1078
1079 Parameters:
1080 :param omega: numpy.ndarray: 角度軸 (omega) の配列 [deg]。
1081 :param intensity_list: list[numpy.ndarray]: プロットする強度配列のリスト。
1082 :param labels: list[str]: 各強度配列に対応する凡例ラベルのリスト。
1083 :param title: str: プロットのタイトル。
1084 :param yscale: str: Y軸のスケール ("linear" または "log")。デフォルトは "log"。
1085 :param text_lines: list[str] or None: プロット内に表示する追加テキスト行のリスト。
1086 :param wait: bool: プロットウィンドウが閉じるまで待機するかどうか。Trueの場合、
1087 ウィンドウが閉じられるまでプログラムの実行がブロックされます。
1088 """
1089 plt.figure()
1090 for intensity, label in zip(intensity_list, labels):
1091 plt.plot(omega, intensity, label=label)
1092
1093 if text_lines:
1094 y0 = 0.90
1095 dy = 0.05
1096 for i, line in enumerate(text_lines):
1097 plt.text(0.55, y0 - i * dy, line, transform=plt.gca().transAxes)
1098
1099 plt.title(title)
1100 plt.yscale(yscale)
1101 plt.xlabel(r"$\omega\ (deg)$")
1102 plt.ylabel("Intensity (arb. u.)")
1103 plt.legend()
1104
1105 if wait:
1106 plt.show()
1107 else:
1108 plt.show()
1109
1110
1111def make_live_plot(omega, int_target, int_init, title, yscale="log"):
1112 """
1113 フィッティングの進捗を表示するためのインタラクティブなライブプロットを作成します。
1114
1115 詳細説明:
1116 初期の実験データ (`int_target`) と初期シミュレーション結果 (`int_init`) を表示し、
1117 フィッティング中に更新される `CurrentFit` のためのラインオブジェクトを準備します。
1118 また、現在のパラメータや残差を表示するためのテキストボックスも作成します。
1119
1120 Parameters:
1121 :param omega: numpy.ndarray: 角度軸 (omega) の配列 [deg]。
1122 :param int_target: numpy.ndarray: 実験データ強度。
1123 :param int_init: numpy.ndarray: 初期シミュレーション強度。
1124 :param title: str: プロットのタイトル。
1125 :param yscale: str: Y軸のスケール ("linear" または "log")。デフォルトは "log"。
1126
1127 Returns:
1128 :returns: tuple[matplotlib.figure.Figure, matplotlib.axes.Axes, matplotlib.lines.Line2D, matplotlib.text.Text]:
1129 (Figureオブジェクト, Axesオブジェクト, CurrentFitのLine2Dオブジェクト, テキストボックスのTextオブジェクト)。
1130 """
1131 plt.ion()
1132 fig, ax = plt.subplots(figsize=(8, 5))
1133 ax.plot(omega, int_target, label="InputData")
1134 ax.plot(omega, int_init, label="InitialSimulation")
1135 line_fit, = ax.plot(omega, int_init, label="CurrentFit")
1136 text_box = ax.text(0.55, 0.92, "", transform=ax.transAxes, va="top", ha="left")
1137
1138 ax.set_title(title)
1139 ax.set_yscale(yscale)
1140 ax.set_xlabel(r"$\omega\ (deg)$")
1141 ax.set_ylabel("Intensity (arb. u.)")
1142 ax.legend()
1143
1144 plt.show(block=False)
1145 fig.canvas.draw()
1146 fig.canvas.flush_events()
1147 plt.pause(0.1)
1148
1149 return fig, ax, line_fit, text_box
1150
1151
1152def update_live_plot(fig, line_fit, text_box, omega, int_fit, text_lines):
1153 """
1154 作成済みのライブプロットの内容を更新します。
1155
1156 詳細説明:
1157 `CurrentFit` のラインデータを新しいフィッティング強度 (`int_fit`) で更新し、
1158 テキストボックスの内容を `text_lines` で更新してプロットを再描画します。
1159 これにより、最適化の進捗がリアルタイムで視覚的に確認できます。
1160
1161 Parameters:
1162 :param fig: matplotlib.figure.Figure: 更新するFigureオブジェクト。
1163 :param line_fit: matplotlib.lines.Line2D: `CurrentFit` のLine2Dオブジェクト。
1164 :param text_box: matplotlib.text.Text: 更新するテキストボックスのTextオブジェクト。
1165 :param omega: numpy.ndarray: 角度軸 (omega) の配列 [deg]。
1166 :param int_fit: numpy.ndarray: 現在のフィッティング強度。
1167 :param text_lines: list[str]: テキストボックスに表示するテキスト行のリスト。
1168 """
1169 line_fit.set_data(omega, int_fit)
1170 text_box.set_text("\n".join(text_lines))
1171 fig.canvas.draw()
1172 fig.canvas.flush_events()
1173 plt.pause(0.001)
1174
1175
1176def show_final_live_plot_wait(fig):
1177 """
1178 ライブプロットを非インタラクティブモードに切り替え、ウィンドウが閉じるまで待機します。
1179
1180 詳細説明:
1181 最適化が完了した後、最終結果を表示したプロットウィンドウを
1182 ユーザーが手動で閉じるまで保持します。
1183
1184 Parameters:
1185 :param fig: matplotlib.figure.Figure: 待機するFigureオブジェクト。
1186 """
1187 plt.ioff()
1188 fig.canvas.draw()
1189 fig.canvas.flush_events()
1190 plt.show()
1191
1192
1193def make_initial_simplex(param_set, optimize_names):
1194 """
1195 Nelder-Mead最適化法のための初期シンプレックスを生成します。
1196
1197 詳細説明:
1198 初期シンプレックスは、`optimize_names` で指定された最適化対象パラメータの
1199 現在の値 (`param_set`) を中心に、N+1個の頂点を持つ形状として生成されます。
1200 各パラメータに対して、現在の値から大きく離れた値を設定した頂点を生成することで、
1201 初期探索空間をカバーします。
1202
1203 Parameters:
1204 :param param_set: dict: 基準となる初期パラメータセット。
1205 :param optimize_names: list[str]: 最適化対象のパラメータ名のリスト。
1206
1207 Returns:
1208 :returns: numpy.ndarray: Nelder-Mead法のための初期シンプレックス。各行が1つの頂点を表します。
1209 """
1210 x, relax, thick = parameter_set_to_values(param_set)
1211 x0 = pack_optimize_values(param_set, optimize_names)
1212
1213 simplex = [x0.copy()]
1214
1215 for idx, name in enumerate(optimize_names):
1216 xnew = x0.copy()
1217
1218 if name == "x":
1219 xnew[idx] = 1.0 if x <= 0.5 else 0.0
1220 elif name == "relax":
1221 xnew[idx] = 1.0 if relax <= 0.5 else 0.0
1222 elif name == "thick":
1223 xnew[idx] = max(thick * 1.25, 1.0) # Thick must be > 0.
1224
1225 simplex.append(xnew)
1226
1227 # thickのみを最適化する場合のシンプレックス調整
1228 # thick * 0.75 と thick * 1.25 で構成
1229 if optimize_names == ["thick"]:
1230 simplex = [
1231 np.array([max(thick * 0.75, 1.0)], dtype=float),
1232 np.array([max(thick * 1.25, 1.0)], dtype=float),
1233 ]
1234
1235 return np.array(simplex, dtype=float)
1236
1237
1238def get_param_bounds(name, current_value):
1239 """
1240 特定のパラメータの探索境界(下限と上限)を取得します。
1241
1242 詳細説明:
1243 組成(`x`)と緩和度(`relax`)はそれぞれ[0.0, 1.0]に制限されます。
1244 膜厚(`thick`)は現在の値を基準に相対的な範囲([0.5*current_value, 1.5*current_value])で、
1245 かつ最低1.0Åという制約が適用されます。
1246 これらはPSOアルゴリズムなどでの粒子の位置更新や初期化に利用されます。
1247
1248 Parameters:
1249 :param name: str: パラメータ名 ("x", "relax", "thick")。
1250 :param current_value: float: そのパラメータの現在の値。
1251
1252 Returns:
1253 :returns: tuple[float, float]: (下限値, 上限値) のタプル。
1254 """
1255 if name == "x":
1256 return 0.0, 1.0
1257 if name == "relax":
1258 return 0.0, 1.0
1259 if name == "thick":
1260 low = max(current_value * 0.5, 1.0)
1261 high = max(current_value * 1.5, low + 1.0) # Ensure high is strictly greater than low if low is 1.0
1262 return low, high
1263 return -np.inf, np.inf # デフォルトでは無限の範囲
1264
1265
1266def randomize_param_set_for_pso(base_param_set, optimize_names):
1267 """
1268 PSOの初期化のために、最適化対象のパラメータをランダムな値に設定した新しいパラメータセットを生成します。
1269
1270 詳細説明:
1271 `base_param_set` をクローンし、`optimize_names` に含まれる各パラメータについて、
1272 `get_param_bounds` で定義された範囲内で一様乱数を用いて値を設定します。
1273
1274 Parameters:
1275 :param base_param_set: dict: 基準となるパラメータセット(最適化対象外の値を保持するために使用)。
1276 :param optimize_names: list[str]: ランダム化する最適化対象のパラメータ名のリスト。
1277
1278 Returns:
1279 :returns: dict: ランダム化された最適化対象パラメータを持つ新しいパラメータセット。
1280 """
1281 ps = clone_parameter_set(base_param_set)
1282 for name in optimize_names:
1283 value = ps[name]["value"]
1284 low, high = get_param_bounds(name, value)
1285 ps[name]["value"] = random.uniform(low, high)
1286 return ps
1287
1288
1289# ============================================================
1290# fit: ランダム探索
1291# ============================================================
1292def fit_random(
1293 substrate,
1294 film,
1295 energy,
1296 omega,
1297 int_target,
1298 param_sets,
1299 parameter_file,
1300 yscale="log",
1301 residual_scale="log",
1302):
1303 """
1304 ランダムウォーク的な手法でXRDフィッティングを行います。
1305
1306 詳細説明:
1307 現在の最適パラメータセットからランダムに少しずつパラメータを変化させ、
1308 残差が改善されればその新しいパラメータセットを採用するという手順を繰り返します。
1309 一定回数改善が見られない場合や、残差が `LIMIT` 以下になれば停止します。
1310 最適化の進捗はライブプロットで視覚的に表示されます。
1311
1312 Parameters:
1313 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
1314 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
1315 :param energy: float: X線エネルギー [eV]。
1316 :param omega: numpy.ndarray: 計算する角度範囲のomega値の配列 [deg]。
1317 :param int_target: numpy.ndarray: 実験データ強度。
1318 :param param_sets: dict: 初期パラメータ、最適化の進捗がここに保存されます。
1319 :param parameter_file: str: パラメータの保存先ファイルパス。
1320 :param yscale: str: プロットのY軸スケール ("linear" または "log")。
1321 :param residual_scale: str: 残差計算に使用するスケール ("log" または "linear")。
1322
1323 Returns:
1324 :returns: dict: 最適化されたパラメータセットを含む `param_sets` 辞書。
1325 """
1326 param_set = clone_parameter_set(param_sets[DEFAULT_PRIMARY_SET])
1327 optimize_names = get_optimize_names(param_set)
1328
1329 if len(optimize_names) == 0:
1330 print("All parameters are fixed. No optimization is performed.")
1331 return param_sets
1332
1333 int_init = calc_pattern_from_param_set(substrate, film, energy, omega, param_set)
1334 int_try_now = int_init.copy()
1335 res = calc_residual(int_target, int_try_now, residual_scale=residual_scale)
1336 rrange = update_rrange(res)
1337
1338 fig, ax, line_fit, text_box = make_live_plot(
1339 omega,
1340 int_target,
1341 int_init,
1342 "膜のXRDフィッティング (Random)",
1343 yscale=yscale,
1344 )
1345
1346 total_count = 0
1347 count = 0
1348
1349 while (res > LIMIT) and (count < MAX_STALL):
1350 new_param_set = clone_parameter_set(param_set)
1351
1352 # 各最適化対象パラメータにランダムな変動を加える
1353 if "x" in optimize_names:
1354 new_param_set["x"]["value"] += random.uniform(-rrange * XRANGE, rrange * XRANGE)
1355 if "relax" in optimize_names:
1356 new_param_set["relax"]["value"] += random.uniform(-rrange * RRANGE, rrange * RRANGE)
1357 if "thick" in optimize_names:
1358 new_param_set["thick"]["value"] += random.uniform(-rrange * TRANGE, rrange * TRANGE)
1359
1360 res_new = objective_from_param_set(
1361 substrate, film, energy, omega, int_target, new_param_set, residual_scale=residual_scale
1362 )
1363
1364 total_count += 1
1365 count += 1
1366
1367 if total_count % NINTERVAL_PRINT == 0:
1368 x, relax, thick = parameter_set_to_values(param_set)
1369 print(
1370 f"{total_count} {count} : "
1371 f"residual= {res:.6f} x= {x:.4f} relax= {relax:.4f} T= {thick:.2f}"
1372 )
1373
1374 if res_new < res:
1375 param_set = new_param_set
1376 int_try_now = calc_pattern_from_param_set(substrate, film, energy, omega, param_set)
1377 res = res_new
1378 rrange = update_rrange(res)
1379 count = 0
1380
1381 if total_count % NINTERVAL_SAVE == 0:
1382 set_best_parameter_set(param_sets, param_set)
1383 save_parameters(parameter_file, param_sets)
1384
1385 if total_count % NINTERVAL_PLOT == 0:
1386 x, relax, thick = parameter_set_to_values(param_set)
1387 update_live_plot(
1388 fig,
1389 line_fit,
1390 text_box,
1391 omega,
1392 int_try_now,
1393 [
1394 f"iter= {total_count}",
1395 f"residual= {res:.6f}",
1396 f"x= {x:.4f}",
1397 f"r= {relax:.4f}",
1398 f"t= {thick:.2f}",
1399 ],
1400 )
1401
1402 set_best_parameter_set(param_sets, param_set)
1403 save_parameters(parameter_file, param_sets)
1404 show_final_live_plot_wait(fig)
1405 return param_sets
1406
1407
1408# ============================================================
1409# fit: scipy minimize
1410# ============================================================
1411def fit_scipy(
1412 substrate,
1413 film,
1414 energy,
1415 omega,
1416 int_target,
1417 param_sets,
1418 parameter_file,
1419 method_name,
1420 nmaxiter,
1421 tol,
1422 yscale="log",
1423 residual_scale="log",
1424):
1425 """
1426 SciPyの最適化アルゴリズム(Nelder-Mead, BFGS, CG)を使用してXRDフィッティングを行います。
1427
1428 詳細説明:
1429 `scipy.optimize.minimize` 関数を利用し、指定された最適化手法でパラメータを調整します。
1430 目的関数は `objective_from_param_set` で定義されており、物理的な制約へのペナルティを含みます。
1431 最適化の進捗はコールバック関数を通じてライブプロットにリアルタイムで表示され、
1432 一定間隔でパラメータがファイルに保存されます。
1433
1434 Parameters:
1435 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
1436 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
1437 :param energy: float: X線エネルギー [eV]。
1438 :param omega: numpy.ndarray: 計算する角度範囲のomega値の配列 [deg]。
1439 :param int_target: numpy.ndarray: 実験データ強度。
1440 :param param_sets: dict: 初期パラメータ、最適化の進捗がここに保存されます。
1441 :param parameter_file: str: パラメータの保存先ファイルパス。
1442 :param method_name: str: 使用するSciPy最適化手法の名前 ("nelder-mead", "bfgs", "cg")。
1443 :param nmaxiter: int: 最大反復回数。
1444 :param tol: float: 収束判定の許容誤差。
1445 :param yscale: str: プロットのY軸スケール ("linear" または "log")。
1446 :param residual_scale: str: 残差計算に使用するスケール ("log" または "linear")。
1447
1448 Returns:
1449 :returns: dict: 最適化されたパラメータセットを含む `param_sets` 辞書。
1450 """
1451 param_set = clone_parameter_set(param_sets[DEFAULT_PRIMARY_SET])
1452 optimize_names = get_optimize_names(param_set)
1453
1454 if len(optimize_names) == 0:
1455 print("All parameters are fixed. No optimization is performed.")
1456 return param_sets
1457
1458 x0 = pack_optimize_values(param_set, optimize_names)
1459 int_init = calc_pattern_from_param_set(substrate, film, energy, omega, param_set)
1460
1461 fig, ax, line_fit, text_box = make_live_plot(
1462 omega,
1463 int_target,
1464 int_init,
1465 f"膜のXRDフィッティング ({method_name})",
1466 yscale=yscale,
1467 )
1468
1469 iter_state = {"count": 0, "best_res": None, "best_param_set": clone_parameter_set(param_set)}
1470
1471 def objective_vec(xvec):
1472 """
1473 最適化アルゴリズムに渡す目的関数 (ベクトル入力用)。
1474
1475 Parameters:
1476 :param xvec: numpy.ndarray: 最適化対象のパラメータ値の配列。
1477 Returns:
1478 :returns: float: 目的関数の評価値。
1479 """
1480 work = unpack_optimize_values(param_set, optimize_names, xvec)
1481 return objective_from_param_set(
1482 substrate, film, energy, omega, int_target, work, residual_scale=residual_scale
1483 )
1484
1485 def callback(xk):
1486 """
1487 最適化の各ステップで呼び出されるコールバック関数。
1488
1489 Parameters:
1490 :param xk: numpy.ndarray: 現在の最適化ステップでのパラメータ値の配列。
1491 """
1492 iter_state["count"] += 1
1493 work = unpack_optimize_values(param_set, optimize_names, xk)
1494 res = objective_from_param_set(
1495 substrate, film, energy, omega, int_target, work, residual_scale=residual_scale
1496 )
1497
1498 if iter_state["best_res"] is None or res < iter_state["best_res"]:
1499 iter_state["best_res"] = res
1500 iter_state["best_param_set"] = clone_parameter_set(work)
1501
1502 x, relax, thick = parameter_set_to_values(work)
1503
1504 if iter_state["count"] % NINTERVAL_PRINT == 0:
1505 print(
1506 f"{iter_state['count']}/{nmaxiter} : "
1507 f"residual= {res:.6f} x= {x:.4f} relax= {relax:.4f} T= {thick:.2f}"
1508 )
1509
1510 if iter_state["count"] % NINTERVAL_SAVE == 0:
1511 set_best_parameter_set(param_sets, iter_state["best_param_set"])
1512 save_parameters(parameter_file, param_sets)
1513
1514 if iter_state["count"] % NINTERVAL_PLOT == 0:
1515 int_fit = calc_pattern_from_param_set(substrate, film, energy, omega, work)
1516 update_live_plot(
1517 fig,
1518 line_fit,
1519 text_box,
1520 omega,
1521 int_fit,
1522 [
1523 f"iter= {iter_state['count']}",
1524 f"residual= {res:.6f}",
1525 f"x= {x:.4f}",
1526 f"r= {relax:.4f}",
1527 f"t= {thick:.2f}",
1528 ],
1529 )
1530
1531 scipy_method = {
1532 "nelder-mead": "Nelder-Mead",
1533 "bfgs": "BFGS",
1534 "cg": "CG",
1535 }[method_name]
1536
1537 options = {"maxiter": nmaxiter, "disp": True}
1538
1539 if method_name == "nelder-mead":
1540 options["initial_simplex"] = make_initial_simplex(param_set, optimize_names)
1541 options["xatol"] = tol # absolute tolerance for changes in x
1542 options["fatol"] = tol # absolute tolerance for changes in fn
1543
1544 result = minimize(
1545 objective_vec,
1546 x0,
1547 method=scipy_method,
1548 callback=callback,
1549 options=options,
1550 tol=tol,
1551 )
1552
1553 final_param_set = unpack_optimize_values(param_set, optimize_names, result.x)
1554 final_res = objective_from_param_set(
1555 substrate, film, energy, omega, int_target, final_param_set, residual_scale=residual_scale
1556 )
1557
1558 # コールバック関数で記録された最良の結果と最終結果を比較
1559 if iter_state["best_res"] is not None and iter_state["best_res"] < final_res:
1560 final_param_set = clone_parameter_set(iter_state["best_param_set"])
1561
1562 set_best_parameter_set(param_sets, final_param_set)
1563 save_parameters(parameter_file, param_sets)
1564 show_final_live_plot_wait(fig)
1565 return param_sets
1566
1567
1568# ============================================================
1569# fit: PSO (粒子群最適化)
1570# ============================================================
1571def fit_pso(
1572 substrate,
1573 film,
1574 energy,
1575 omega,
1576 int_target,
1577 param_sets,
1578 parameter_file,
1579 nmaxiter,
1580 tol,
1581 nparticles,
1582 w,
1583 c1,
1584 c2,
1585 yscale="log",
1586 residual_scale="log",
1587 pso_stall_max=20,
1588 pso_spread_rtol=0.01,
1589):
1590 """
1591 粒子群最適化 (PSO) を使用してXRDフィッティングを行います。
1592
1593 詳細説明:
1594 この関数は、粒子群最適化アルゴリズムを実装し、複数の「粒子」が解空間を探索します。
1595 各粒子は、自身の最良の位置 (`pbest`) と群全体の最良の位置 (`gbest`) に基づいて
1596 速度と位置を更新します。一定期間の改善なし (`pso_stall_max`) または
1597 粒子の位置の分散が閾値以下になった場合 (`pso_spread_rtol`) に早期停止します。
1598 最適化の進捗はライブプロットで視覚的に表示され、一定間隔でパラメータがファイルに保存されます。
1599
1600 Parameters:
1601 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
1602 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
1603 :param energy: float: X線エネルギー [eV]。
1604 :param omega: numpy.ndarray: 計算する角度範囲のomega値の配列 [deg]。
1605 :param int_target: numpy.ndarray: 実験データ強度。
1606 :param param_sets: dict: 初期パラメータ、最適化の進捗がここに保存されます。
1607 :param parameter_file: str: パラメータの保存先ファイルパス。
1608 :param nmaxiter: int: 最大反復回数。
1609 :param tol: float: 収束判定の許容誤差(残差の改善量に対する閾値)。
1610 :param nparticles: int: 粒子の総数。
1611 :param w: float: PSOの慣性重み。
1612 :param c1: float: PSOの自己学習係数。
1613 :param c2: float: PSOの社会学習係数。
1614 :param yscale: str: プロットのY軸スケール ("linear" または "log")。
1615 :param residual_scale: str: 残差計算に使用するスケール ("log" または "linear")。
1616 :param pso_stall_max: int: 改善が見られない場合にPSOを停止する最大反復数。
1617 :param pso_spread_rtol: float: 粒子の分散に基づく停止条件の相対許容誤差。
1618
1619 Returns:
1620 :returns: dict: 最適化されたパラメータセットを含む `param_sets` 辞書。
1621 """
1622 base_param_set = clone_parameter_set(param_sets[DEFAULT_PRIMARY_SET])
1623 optimize_names = get_optimize_names(base_param_set)
1624
1625 if len(optimize_names) == 0:
1626 print("All parameters are fixed. No optimization is performed.")
1627 return param_sets
1628
1629 int_init = calc_pattern_from_param_set(substrate, film, energy, omega, base_param_set)
1630
1631 fig, ax, line_fit, text_box = make_live_plot(
1632 omega,
1633 int_target,
1634 int_init,
1635 "膜のXRDフィッティング (PSO)",
1636 yscale=yscale,
1637 )
1638
1639 seed_particles = get_pso_parameter_sets(param_sets)
1640 particles = [clone_parameter_set(ps) for ps in seed_particles]
1641
1642 # 必要に応じて粒子数を増やす
1643 while len(particles) < nparticles:
1644 particles.append(randomize_param_set_for_pso(base_param_set, optimize_names))
1645
1646 # 必要に応じて粒子数を減らす
1647 if len(particles) > nparticles:
1648 particles = particles[:nparticles]
1649
1650 positions = []
1651 velocities = []
1652 pbest_positions = []
1653 pbest_scores = []
1654
1655 for ps in particles:
1656 pos = pack_optimize_values(ps, optimize_names)
1657 vel = np.zeros_like(pos)
1658
1659 # 速度の初期化
1660 for i, name in enumerate(optimize_names):
1661 val = ps[name]["value"]
1662 low, high = get_param_bounds(name, val)
1663 span = high - low
1664 # 初期速度を探索範囲の10%程度でランダムに設定
1665 vel[i] = random.uniform(-0.1 * span, 0.1 * span)
1666
1667 score = objective_from_param_set(
1668 substrate, film, energy, omega, int_target, ps, residual_scale=residual_scale
1669 )
1670
1671 positions.append(pos.copy())
1672 velocities.append(vel.copy())
1673 pbest_positions.append(pos.copy())
1674 pbest_scores.append(score)
1675
1676 positions = np.array(positions, dtype=float)
1677 velocities = np.array(velocities, dtype=float)
1678 pbest_positions = np.array(pbest_positions, dtype=float)
1679 pbest_scores = np.array(pbest_scores, dtype=float)
1680
1681 gbest_idx = int(np.argmin(pbest_scores))
1682 gbest_position = pbest_positions[gbest_idx].copy()
1683 gbest_score = float(pbest_scores[gbest_idx])
1684
1685 iter_count = 0
1686 prev_best = gbest_score
1687 no_improve_count = 0
1688
1689 base_x, base_relax, base_thick = parameter_set_to_values(base_param_set)
1690 # 粒子の分散許容誤差をパラメータごとに調整
1691 spread_tol_map = {
1692 "x": pso_spread_rtol * 1.0, # xは[0,1]なので相対値そのまま
1693 "relax": pso_spread_rtol * 1.0, # relaxも[0,1]なので相対値そのまま
1694 "thick": pso_spread_rtol * base_thick, # thickは絶対値なので、基準値に対する相対値
1695 }
1696
1697 while iter_count < nmaxiter:
1698 iter_count += 1
1699
1700 for p in range(nparticles):
1701 for i, name in enumerate(optimize_names):
1702 current_val = positions[p, i]
1703 low, high = get_param_bounds(name, current_val)
1704
1705 r1 = random.random()
1706 r2 = random.random()
1707
1708 # 速度の更新
1709 velocities[p, i] = (
1710 w * velocities[p, i]
1711 + c1 * r1 * (pbest_positions[p, i] - positions[p, i])
1712 + c2 * r2 * (gbest_position[i] - positions[p, i])
1713 )
1714
1715 # 位置の更新
1716 positions[p, i] += velocities[p, i]
1717
1718 # 境界条件処理
1719 if positions[p, i] < low:
1720 positions[p, i] = low
1721 velocities[p, i] *= -0.5 # 境界で跳ね返す
1722 elif positions[p, i] > high:
1723 positions[p, i] = high
1724 velocities[p, i] *= -0.5 # 境界で跳ね返す
1725
1726 work = unpack_optimize_values(base_param_set, optimize_names, positions[p])
1727 score = objective_from_param_set(
1728 substrate, film, energy, omega, int_target, work, residual_scale=residual_scale
1729 )
1730
1731 # pbestの更新
1732 if score < pbest_scores[p]:
1733 pbest_scores[p] = score
1734 pbest_positions[p] = positions[p].copy()
1735
1736 # gbestの更新
1737 if score < gbest_score:
1738 gbest_score = float(score)
1739 gbest_position = positions[p].copy()
1740
1741 best_param_set = unpack_optimize_values(base_param_set, optimize_names, gbest_position)
1742 best_int = calc_pattern_from_param_set(substrate, film, energy, omega, best_param_set)
1743 best_x, best_relax, best_thick = parameter_set_to_values(best_param_set)
1744
1745 if iter_count % NINTERVAL_PRINT == 0:
1746 print(
1747 f"{iter_count}/{nmaxiter} : residual= {gbest_score:.6f} "
1748 f"x= {best_x:.4f} relax= {best_relax:.4f} T= {best_thick:.2f}"
1749 )
1750
1751 if iter_count % NINTERVAL_SAVE == 0:
1752 set_best_parameter_set(param_sets, best_param_set)
1753 # 現在の粒子群の最良なものを保存
1754 particle_sets = []
1755 order = np.argsort(pbest_scores)
1756 for idx in order[:nparticles]: # 上位nparticles個の粒子を保存
1757 ps = unpack_optimize_values(base_param_set, optimize_names, pbest_positions[idx])
1758 particle_sets.append(ps)
1759 param_sets = replace_pso_parameter_sets(param_sets, particle_sets)
1760 save_parameters(parameter_file, param_sets)
1761
1762 if iter_count % NINTERVAL_PLOT == 0:
1763 update_live_plot(
1764 fig,
1765 line_fit,
1766 text_box,
1767 omega,
1768 best_int,
1769 [
1770 f"iter= {iter_count}",
1771 f"residual= {gbest_score:.6f}",
1772 f"x= {best_x:.4f}",
1773 f"r= {best_relax:.4f}",
1774 f"t= {best_thick:.2f}",
1775 ],
1776 )
1777
1778 # 停止条件のチェック
1779 improve = prev_best - gbest_score
1780 if improve < tol: # 残差の改善が許容誤差未満
1781 no_improve_count += 1
1782 else:
1783 no_improve_count = 0
1784
1785 # 粒子の分散が十分に小さいかチェック
1786 spread_ok = True
1787 spread_info = {} # デバッグ用
1788 for i, name in enumerate(optimize_names):
1789 spread = float(np.max(positions[:, i]) - np.min(positions[:, i]))
1790 spread_info[name] = spread
1791 if spread > spread_tol_map[name]:
1792 spread_ok = False
1793
1794 if no_improve_count >= pso_stall_max:
1795 print("PSO stop: stall criterion satisfied.")
1796 break
1797 if spread_ok:
1798 print("PSO stop: spread criterion satisfied.")
1799 break
1800 prev_best = gbest_score
1801
1802 set_best_parameter_set(param_sets, best_param_set)
1803 save_parameters(parameter_file, param_sets)
1804 show_final_live_plot_wait(fig)
1805 return param_sets
1806
1807
1808# ============================================================
1809# guessモード ヘルパー
1810# ============================================================
1811def ensure_valid_savgol(points, order, ndata):
1812 """
1813 Savitzky-Golayフィルタのパラメータがデータ長に対して妥当であることを保証します。
1814
1815 詳細説明:
1816 ウィンドウサイズ `points` は奇数であり、データ長 `ndata` を超えないように調整されます。
1817 多項式次数 `order` はウィンドウサイズ `points` より小さく調整されます。
1818
1819 Parameters:
1820 :param points: int: Savitzky-Golayフィルタのウィンドウ点数。
1821 :param order: int: Savitzky-Golayフィルタの多項式次数。
1822 :param ndata: int: 入力データの総点数。
1823
1824 Returns:
1825 :returns: tuple[int, int]: 調整された (ウィンドウ点数, 多項式次数)。
1826 """
1827 points = int(points)
1828 order = int(order)
1829 if points < 3: points = 3
1830 if points % 2 == 0: points += 1 # Ensure odd window length
1831 if points > ndata: points = ndata if ndata % 2 == 1 else ndata - 1
1832 if order >= points: order = points - 1
1833 return points, order
1834
1835
1836def estimate_thickness_from_fft_signal(omega_deg, signal, wavelength, keep_n=5, nq_uniform=DEFAULT_NQ_UNIFORM):
1837 """
1838 信号に対してFFTを行い、主要な周波数成分から膜厚の候補を算出します。
1839
1840 詳細説明:
1841 入力の角度データと信号をq軸に変換し、一様間隔で補間します。
1842 ハニング窓を適用した後、FFTを実行して周波数スペクトルを取得します。
1843 スペクトル中の顕著なピークを検出し、その周波数から膜厚候補を計算します。
1844 複数の候補をスコアの高い順にソートして返します。
1845
1846 Parameters:
1847 :param omega_deg: numpy.ndarray: 角度データ [deg]。
1848 :param signal: numpy.ndarray: 解析対象の信号 (通常はエンベロープ除去後の残差)。
1849 :param wavelength: float: X線波長 [Å]。
1850 :param keep_n: int: 保持する膜厚候補の最大数。
1851 :param nq_uniform: int: FFTのために一様補間するq軸の点数。
1852
1853 Returns:
1854 :returns: tuple[list[dict], numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]:
1855 - `candidates_list`: 膜厚候補のリスト。各候補は辞書 (`method`, `thick`, `score`, `dq`, `freq`)。
1856 - `qz_axis`: 一様化されたqz軸。
1857 - `signal_uniform`: 一様化された信号。
1858 - `freq_axis`: FFTの周波数軸。
1859 - `fft_amplitude`: FFTの振幅。
1860 """
1861 theta_rad = np.deg2rad(omega_deg)
1862 qz = 4.0 * np.pi / wavelength * np.sin(theta_rad)
1863
1864 idx = np.argsort(qz)
1865 qz = qz[idx]
1866 signal = signal[idx]
1867
1868 # q軸を一様間隔に補間
1869 qz_uniform = np.linspace(qz.min(), qz.max(), nq_uniform)
1870 y_uniform = np.interp(qz_uniform, qz, signal)
1871 y_uniform = y_uniform - np.mean(y_uniform) # DC成分除去
1872
1873 dq = qz_uniform[1] - qz_uniform[0]
1874 fft_vals = np.fft.rfft(y_uniform * np.hanning(len(y_uniform))) # ハニング窓適用
1875 fft_amp = np.abs(fft_vals)
1876 freq = np.fft.rfftfreq(len(y_uniform), d=dq)
1877
1878 if len(fft_amp) > 0: fft_amp[0] = 0.0 # DC成分をゼロにする
1879
1880 # FFTスペクトルからピークを検出
1881 peak_idx, props = find_peaks(fft_amp, prominence=np.max(fft_amp) * 0.05 if np.max(fft_amp)>0 else 0)
1882
1883 candidates = []
1884 if len(peak_idx) > 0:
1885 order = np.argsort(props.get("prominences", np.ones(len(peak_idx))))[::-1] # Prominenceでソート
1886 for idxp in peak_idx[order][:keep_n]:
1887 f = freq[idxp]
1888 if f <= 0: continue # 周波数が0以下のものは無視
1889 dq_val = 1.0 / f
1890 thick = 2.0 * np.pi / dq_val # 膜厚 = 2*pi / dq
1891 candidates.append({"method": "fft", "thick": float(thick), "score": float(fft_amp[idxp]), "dq": float(dq_val), "freq": float(f)})
1892
1893 return sorted(candidates, key=lambda d: d["score"], reverse=True), qz_uniform, y_uniform, freq, fft_amp
1894
1895
1896def assign_fringe_index_auto(q_vals, dq_est):
1897 """
1898 FFTによる推定周期 `dq_est` に基づき、各ピークにフリンジ次数 (n) を自動的に割り当てます。
1899
1900 詳細説明:
1901 フリンジのQ値 (`q_vals`) の差分と推定された周期 (`dq_est`) を比較し、
1902 最も近い整数倍をフリンジ次数のステップとして採用します。
1903
1904 Parameters:
1905 :param q_vals: numpy.ndarray: 検出されたフリンジピークのQ値の配列。
1906 :param dq_est: float: FFTによって推定されたフリンジ周期 (Δq)。
1907
1908 Returns:
1909 :returns: numpy.ndarray: 各ピークに割り当てられたフリンジ次数 (n) の配列。
1910 """
1911 if len(q_vals) == 0: return np.array([], dtype=int)
1912 n_vals = [0] # 最初のピークを0次としてスタート
1913 for i in range(1, len(q_vals)):
1914 step = int(round((q_vals[i] - q_vals[i - 1]) / dq_est))
1915 n_vals.append(n_vals[-1] + max(step, 1)) # 少なくとも1次ずつ増えることを保証
1916 return np.array(n_vals, dtype=int)
1917
1918
1919def linear_fit_two_stage(n_vals, q_vals):
1920 """
1921 フリンジ次数 (n) と q 値の関係を2段階の線形回帰で解析し、傾きから膜厚を求めます。
1922
1923 詳細説明:
1924 1. 全てのピークデータを用いて線形回帰を行い、初期の残差と標準偏差を計算します。
1925 2. 初期残差が標準偏差の2倍を超える外れ値を除外し、残りのデータで再度線形回帰を行います。
1926 3. 最終的な回帰の傾き (dq/dn) から、膜厚 (Thickness = 2π / (dq/dn)) を計算します。
1927
1928 Parameters:
1929 :param n_vals: numpy.ndarray: フリンジ次数の配列。
1930 :param q_vals: numpy.ndarray: 対応するQ値の配列。
1931
1932 Returns:
1933 :returns: dict or None: 解析結果を含む辞書。ピークが2つ未満の場合はNone。
1934 - `coef1`: 1段階目の回帰係数。
1935 - `coef2`: 2段階目の回帰係数。
1936 - `used_stage1`: 1段階目で使用されたピークのブール配列。
1937 - `used_stage2`: 2段階目で使用されたピークのブール配列。
1938 - `resid_stage1`: 1段階目の残差。
1939 - `resid_stage2`: 2段階目の残差。
1940 - `thickness`: 計算された膜厚 [Å]。
1941 """
1942 if len(n_vals) < 2: return None # 2点以上必要
1943
1944 # Stage 1: 全てのデータで線形回帰
1945 coef1 = np.polyfit(n_vals, q_vals, 1)
1946 resid1 = q_vals - np.polyval(coef1, n_vals)
1947 sigma1 = np.std(resid1, ddof=1) if len(resid1) >= 2 else 0 # 標本標準偏差 (ddof=1)
1948
1949 # Stage 2: 外れ値除去後に再回帰
1950 if sigma1 > 0:
1951 used2 = np.abs(resid1) < 2.0 * sigma1 # 2σ以内に絞る
1952 else: # sigma1が0の場合、全て使用(データ点が少ないなど)
1953 used2 = np.ones(len(n_vals), dtype=bool)
1954
1955 if np.sum(used2) < 2: # 除去後も2点以上必要
1956 used2 = np.ones(len(n_vals), dtype=bool) # 2点未満なら全て使用に戻す
1957
1958 coef2 = np.polyfit(n_vals[used2], q_vals[used2], 1)
1959
1960 # 傾きから膜厚を計算 (dq/dn) -> thickness = 2*pi / |slope|
1961 thickness = 2.0 * np.pi / abs(coef2[0]) if coef2[0] != 0 else np.nan
1962
1963 return {
1964 "coef1": coef1,
1965 "coef2": coef2,
1966 "used_stage1": np.ones(len(n_vals), dtype=bool), # 常に全てTrue
1967 "used_stage2": used2,
1968 "resid_stage1": resid1,
1969 "resid_stage2": q_vals - np.polyval(coef2, n_vals),
1970 "thickness": thickness
1971 }
1972
1973
1974def detect_fringe_clusters(q_vals, dq_ref, gap_factor=DEFAULT_CLUSTER_GAP_FACTOR):
1975 """
1976 フリンジの欠損が大きい箇所でデータを分割し、クラスター化します。
1977
1978 詳細説明:
1979 検出されたQ値の差分が参照周期 `dq_ref` に `gap_factor` を掛けた値よりも大きい場合、
1980 その箇所でフリンジピークのシーケンスが途切れていると判断し、データを複数のクラスターに分割します。
1981 これにより、不連続なフリンジパターンでも個別に解析できるようになります。
1982 各クラスターは少なくとも2つのピークを含む必要があります。
1983
1984 Parameters:
1985 :param q_vals: numpy.ndarray: 検出されたフリンジピークのQ値の配列。
1986 :param dq_ref: float: フリンジ周期の参照値 (FFTから得られた推定値など)。
1987 :param gap_factor: float: クラスター分割の閾値を決定するための係数。
1988
1989 Returns:
1990 :returns: list[numpy.ndarray]: 各クラスターに属するピークのインデックスの配列のリスト。
1991 """
1992 if len(q_vals) < 2: return [np.arange(len(q_vals))]
1993 diffs = np.diff(q_vals)
1994 # 差が基準値より大きい箇所で分割
1995 split_idx = np.where(diffs > gap_factor * dq_ref)[0]
1996 clusters = []
1997 start = 0
1998 for idx in split_idx:
1999 clusters.append(np.arange(start, idx + 1))
2000 start = idx + 1
2001 clusters.append(np.arange(start, len(q_vals)))
2002 return [cl for cl in clusters if len(cl) >= 2] # 2点以上のクラスターのみを保持
2003
2004
2005def build_cluster_peak_tables(omega_cut, residual_for_peaks, wavelength, dq_ref, gap_factor):
2006 """
2007 フリンジピークを検出し、検出されたピークをクラスター化し、
2008 各クラスターで線形回帰分析を実行します。
2009
2010 詳細説明:
2011 1. 残差信号からフリンジピークを検出します。
2012 2. 検出されたピークのQ値を計算します。
2013 3. `detect_fringe_clusters` を使用して、ピークを論理的なクラスターに分割します。
2014 4. 各クラスター内で `assign_fringe_index_auto` でフリンジ次数を割り当て、
2015 `linear_fit_two_stage` で線形回帰を行い、膜厚を推定します。
2016 5. 検出されたピークの詳細 (`all_peak_rows`) とクラスターごとの解析結果 (`cluster_rows`)
2017 および回帰結果 (`cluster_fit_results`) を生成します。
2018
2019 Parameters:
2020 :param omega_cut: numpy.ndarray: 角度範囲を限定したomegaの配列 [deg]。
2021 :param residual_for_peaks: numpy.ndarray: ピーク検出に使用する残差信号。
2022 :param wavelength: float: X線波長 [Å]。
2023 :param dq_ref: float: フリンジ周期の参照値 (Δq)。
2024 :param gap_factor: float: クラスター分割の閾値を決定するための係数。
2025
2026 Returns:
2027 :returns: tuple[list[dict], list[dict], list[dict]]:
2028 - `all_peak_rows`: 検出された全ピークの詳細情報。
2029 - `cluster_rows`: 各クラスターごとの解析サマリー。
2030 - `cluster_fit_results`: 各クラスターの線形回帰の詳細結果。
2031 ピークが検出されない場合は、空のリストとNoneが返されます。
2032 """
2033 # 残差信号からピークを検出
2034 peak_idx, props = find_peaks(residual_for_peaks, prominence=np.max(residual_for_peaks) * 0.02 if np.max(residual_for_peaks)>0 else 0)
2035 if len(peak_idx) == 0: return [], [], None
2036
2037 # プロミネンスが高いピークから最大30個を選択し、角度順にソート
2038 order = np.argsort(props.get("prominences", np.ones(len(peak_idx))))[::-1]
2039 peak_idx = np.sort(peak_idx[order][:30])
2040
2041 theta_deg = omega_cut[peak_idx]
2042 q_vals = 4.0 * np.pi / wavelength * np.sin(np.deg2rad(theta_deg))
2043
2044 # ピークをクラスターに分割
2045 clusters = detect_fringe_clusters(q_vals, dq_ref, gap_factor)
2046
2047 all_peak_rows, cluster_rows, cluster_fit_results = [], [], []
2048
2049 for cluster_id, cl in enumerate(clusters):
2050 q_cl, twotheta_cl = q_vals[cl], 2.0 * theta_deg[cl]
2051 n_cl = assign_fringe_index_auto(q_cl, dq_ref)
2052 fit_result = linear_fit_two_stage(n_cl, q_cl)
2053 if fit_result is None: continue # 回帰ができなかった場合、このクラスターはスキップ
2054
2055 cluster_fit_results.append({"cluster_id": cluster_id, "n_vals": n_cl, "q_vals": q_cl, "fit_result": fit_result})
2056 for i in range(len(q_cl)):
2057 all_peak_rows.append({
2058 "cluster_id": cluster_id, "n": int(n_cl[i]), "q": float(q_cl[i]), "twotheta": float(twotheta_cl[i]),
2059 "used_stage1": True, "used_stage2": bool(fit_result["used_stage2"][i]),
2060 "resid_stage1": float(fit_result["resid_stage1"][i]), "resid_stage2": float(fit_result["resid_stage2"][i])
2061 })
2062 cluster_rows.append({
2063 "cluster_id": cluster_id, "npeaks": len(q_cl), "dq_est": float(dq_ref),
2064 "thick_fft": float(2.0 * np.pi / dq_ref), "thick_reg": float(fit_result["thickness"]),
2065 "confidence": float(len(q_cl)) # クラスター内のピーク数で信頼度を表現
2066 })
2067 return all_peak_rows, cluster_rows, cluster_fit_results
2068
2069
2070# ============================================================
2071# guess モード 本体
2072# ============================================================
2073def guess_thickness(
2074 substrate, film, energy, omega, intensity, parameter_file, param_sets,
2075 guess_low, guess_high, nsmooth_points, nsmooth_order, nguess_keep,
2076 cluster_gap_factor=DEFAULT_CLUSTER_GAP_FACTOR, yscale="log", infile=""
2077):
2078 """
2079 XRDフリンジ解析に基づいて膜厚を推定します。解析結果をプロットし、上位候補をパラメータCSVに保存します。
2080
2081 詳細説明:
2082 1. 指定された角度範囲でXRDデータの一部を切り出します。
2083 2. Savitzky-Golayフィルタを使用して強度データからエンベロープを除去し、残差信号を抽出します。
2084 3. 残差信号にFFTを適用し、主要な周波数成分から膜厚の初期候補 (Δq_est) を算出します。
2085 4. 残差信号からフリンジピークを検出し、Δq_estとギャップ係数に基づいてピークをクラスターに分割します。
2086 5. 各クラスター内でフリンジ次数を割り当て、線形回帰分析を行い、最終的な膜厚候補を算出します。
2087 6. 検出されたピーク情報とクラスター情報をCSVファイルに保存します。
2088 7. FFTと線形回帰の両方から得られた膜厚候補をスコア順にソートし、上位の候補をコンソールに表示します。
2089 8. 最もスコアの高い膜厚候補をプライマリパラメータセットの膜厚値として設定し、
2090 上位の候補はPSO粒子としてパラメータファイルに保存されます。
2091 9. 解析の主要なステップ(信号・残差、FFTスペクトル、n-qプロット)を視覚化します。
2092
2093 Parameters:
2094 :param substrate: xrayutilities.materials.Crystal: 基板材料オブジェクト。
2095 :param film: xrayutilities.materials.Crystal: 膜材料オブジェクト。
2096 :param energy: float: X線エネルギー [eV]。
2097 :param omega: numpy.ndarray: 全角度範囲のomega値の配列 [deg]。
2098 :param intensity: numpy.ndarray: 全強度データ。
2099 :param parameter_file: str: パラメータの保存先ファイルパス。
2100 :param param_sets: dict: パラメータセット(推定結果がここに保存されます)。
2101 :param guess_low: float: 解析を開始する角度 [deg]。
2102 :param guess_high: float: 解析を終了する角度 [deg]。
2103 :param nsmooth_points: int: Savitzky-Golayフィルタのウィンドウ点数。
2104 :param nsmooth_order: int: Savitzky-Golayフィルタの多項式次数。
2105 :param nguess_keep: int: 保持する膜厚候補の数。
2106 :param cluster_gap_factor: float: フリンジクラスター判定用のギャップ係数。
2107 :param yscale: str: プロットのY軸スケール ("linear" または "log")。
2108 :param infile: str: 入力データファイル名(出力ファイル名生成に使用)。
2109
2110 Returns:
2111 :returns: dict: 更新されたパラメータセットを含む `param_sets` 辞書。
2112 """
2113 mask = (omega >= guess_low) & (omega <= guess_high)
2114 if np.sum(mask) < 10:
2115 print("Error: guess range contains too few points.")
2116 return param_sets
2117
2118 omega_cut, int_cut = omega[mask], intensity[mask]
2119 y_signal = np.log(np.maximum(int_cut, 1e-30)) if yscale == "log" else int_cut.copy()
2120
2121 # Savitzky-Golayフィルタのパラメータを調整
2122 npts, norder = ensure_valid_savgol(nsmooth_points, nsmooth_order, len(y_signal))
2123 envelope = savgol_filter(y_signal, window_length=npts, polyorder=norder)
2124 residual = y_signal - envelope
2125
2126 wavelength = xu.wavelength("CuKa1") # Cu Kα1波長を取得
2127
2128 # FFTによる膜厚推定
2129 fft_cands, _, _, freq, fft_amp = estimate_thickness_from_fft_signal(omega_cut, residual, wavelength, keep_n=max(nguess_keep, 10))
2130 if not fft_cands:
2131 print("Warning: No FFT candidates found for thickness estimation.")
2132 return param_sets
2133
2134 dq_ref = fft_cands[0]["dq"] # FFTで最も支配的なΔqを基準とする
2135
2136 # ピーク検出とクラスター化、線形回帰
2137 # 残差が負の値にならないように調整してピーク検出
2138 peak_rows, cluster_rows, cluster_fits = build_cluster_peak_tables(omega_cut, np.maximum(residual - np.min(residual), 0), wavelength, dq_ref, cluster_gap_factor)
2139
2140 if peak_rows: save_guess_peak_csv(get_guess_peaks_filename(infile), peak_rows)
2141 if cluster_rows: save_guess_cluster_csv(get_guess_clusters_filename(infile), cluster_rows)
2142
2143 # 全ての膜厚候補を収集し、スコアでソート
2144 all_cands = []
2145 for c in fft_cands[:nguess_keep]:
2146 all_cands.append({"method": "fft", "thick": c["thick"], "score": c["score"]})
2147 for r in cluster_rows:
2148 if np.isfinite(r["thick_reg"]): # 回帰による膜厚が有効な場合のみ追加
2149 all_cands.append({"method": f"cl{r['cluster_id']}-reg", "thick": r["thick_reg"], "score": r["confidence"]})
2150
2151 all_cands = sorted(all_cands, key=lambda x: x["score"], reverse=True)
2152
2153 if not all_cands:
2154 print("Warning: No valid thickness candidates found.")
2155 return param_sets
2156
2157 # 最良の膜厚をプライマリセットに設定
2158 best_ps = clone_parameter_set(param_sets[DEFAULT_PRIMARY_SET])
2159 best_ps["thick"]["value"] = all_cands[0]["thick"]
2160 set_best_parameter_set(param_sets, best_ps)
2161
2162 # 上位の候補をPSOの粒子としてパラメータファイルに保存
2163 pso_cands = []
2164 for c in all_cands[:nguess_keep]:
2165 ps = clone_parameter_set(param_sets[DEFAULT_PRIMARY_SET])
2166 ps["thick"]["value"] = c["thick"]
2167 pso_cands.append(ps)
2168 param_sets = replace_pso_parameter_sets(param_sets, pso_cands)
2169 save_parameters(parameter_file, param_sets)
2170
2171 print("推定膜厚候補:")
2172 for i, c in enumerate(all_cands[:10]): # 上位10件を表示
2173 print(f" {i:2d} : {c['method']:10s} thick= {c['thick']:.2f} A")
2174
2175 # プロット生成
2176 plt.ion() # インタラクティブモードON
2177
2178 # Figure 1: Signal/Envelope/Residual
2179 fig1, ax1 = plt.subplots(figsize=(8, 5))
2180 ax1.plot(omega_cut, y_signal, label="signal")
2181 ax1.plot(omega_cut, envelope, label="envelope")
2182 ax1.plot(omega_cut, residual, label="residual")
2183 ax1.legend(); ax1.set_title("Fringe Analysis: Signal & Residual")
2184 ax1.set_xlabel(r"$\omega\ (deg)$")
2185 ax1.set_ylabel("Intensity (arb. u.)")
2186 ax1.set_yscale(yscale)
2187
2188
2189 # Figure 2: FFT Spectrum
2190 fig2, ax2 = plt.subplots(figsize=(8, 5))
2191 if len(freq) > 1 and len(fft_amp) > 1: # DC成分以外を表示
2192 ax2.plot(freq[1:], fft_amp[1:])
2193 ax2.set_title("FFT Spectrum")
2194 ax2.set_xlabel(r"Frequency ($1/\AA^{-1}$)")
2195 ax2.set_ylabel("Amplitude")
2196
2197
2198 # Figure 3: N-Q plot (フリンジ次数 vs Q値)
2199 if cluster_fits:
2200 fig3, ax3 = plt.subplots(figsize=(7, 5))
2201 for f in cluster_fits:
2202 # Stage 2で採用されたピークのみを強調表示
2203 used_peaks_q = f["q_vals"][f["fit_result"]["used_stage2"]]
2204 used_peaks_n = f["n_vals"][f["fit_result"]["used_stage2"]]
2205 rejected_peaks_q = f["q_vals"][~f["fit_result"]["used_stage2"]]
2206 rejected_peaks_n = f["n_vals"][~f["fit_result"]["used_stage2"]]
2207
2208 ax3.plot(used_peaks_n, used_peaks_q, 'o', label=f"cl {f['cluster_id']} (used)")
2209 if len(rejected_peaks_n) > 0:
2210 ax3.plot(rejected_peaks_n, rejected_peaks_q, 'x', color=ax3.lines[-1].get_color(), alpha=0.5, label=f"cl {f['cluster_id']} (rejected)")
2211
2212 # 回帰直線を描画
2213 ax3.plot(f["n_vals"], np.polyval(f["fit_result"]["coef2"], f["n_vals"]), '--', color=ax3.lines[-1].get_color(), label=f"Fit cl {f['cluster_id']}")
2214
2215 ax3.legend(); ax3.set_title("Linear Regression: n vs q")
2216 ax3.set_xlabel("Fringe Order (n)")
2217 ax3.set_ylabel(r"Q ($1/\AA^{-1}$)")
2218
2219 plt.ioff(); plt.show() # 非インタラクティブモードに戻し、全てのプロットを表示
2220 return param_sets
2221
2222
2223# ============================================================
2224# メイン実行ルーチン
2225# ============================================================
2226def main(args):
2227 """
2228 コマンドライン引数に基づき、XRDシミュレーション、データ読み込み、フィッティング、または膜厚推定を実行します。
2229
2230 詳細説明:
2231 この関数は、プログラムのエントリポイントであり、以下の処理フローに従います。
2232 1. 基板と膜の材料モデルを準備します。
2233 2. パラメータファイルから初期パラメータを読み込み、固定パラメータを適用して保存します。
2234 3. `args.mode` に応じて、以下のいずれかの操作を実行します。
2235 - "read": 2theta-intensityデータを読み込み、プロットします。
2236 - "sim": 指定されたパラメータでXRDパターンをシミュレートし、プロットします。
2237 入力ファイルがある場合は実験データと比較してプロットします。
2238 - "guess": フリンジ解析に基づいて膜厚を推定し、結果をプロットしてパラメータファイルを更新します。
2239 - "fit": 指定された最適化手法 (random, nelder-mead, bfgs, cg, pso) を使用して、
2240 実験データにフィッティングを行い、パラメータを最適化します。
2241 フィッティングの進捗はライブプロットで表示されます。
2242
2243 Parameters:
2244 :param args: argparse.Namespace: コマンドライン引数をパースした結果のオブジェクト。
2245 """
2246 substrate, film = prepare_materials(args.substrate_file, args.film_file)
2247 # Cu Kα1波長を使用してX線エネルギーを計算
2248 energy = 12390.0 / xu.wavelength("CuKa1")
2249
2250 parameter_file = get_parameter_filename(args.infile)
2251 param_sets = read_parameters(parameter_file)
2252 apply_fix_list(param_sets, parse_fix_list(args.fix))
2253 save_parameters(parameter_file, param_sets) # 初期/読み込み済みパラメータを保存
2254
2255 if args.mode == "read":
2256 twotheta, omega, intensity = read_data(args.infile, substrate, film, energy)
2257 plot_xrd(omega, [intensity], ["ReadData"], "XRDデータ読込", yscale=args.yscale, wait=True)
2258
2259 elif args.mode == "sim":
2260 sim_ps = param_sets[DEFAULT_PRIMARY_SET]
2261 twotheta_sim, omega_sim = make_scan_axis()
2262 int_sim = calc_pattern_from_param_set(substrate, film, energy, omega_sim, sim_ps)
2263 if args.infile:
2264 # 入力ファイルがある場合、それを読み込みシミュレーション結果と比較プロット
2265 _, omega_ref, int_ref = read_data(args.infile, substrate, film, energy)
2266 plot_xrd(omega_sim, [int_ref, int_sim], ["Data", "Sim"], "シミュレーション比較", yscale=args.yscale, wait=True)
2267 else:
2268 # 入力ファイルがない場合、シミュレーション結果のみをプロット
2269 plot_xrd(omega_sim, [int_sim], ["Sim"], "XRDシミュレーション", yscale=args.yscale, wait=True)
2270
2271 elif args.mode == "guess":
2272 _, omega, intensity = read_data(args.infile, substrate, film, energy)
2273 guess_thickness(substrate, film, energy, omega, intensity, parameter_file, param_sets, args.guess_low, args.guess_high, args.nsmooth_points, args.nsmooth_order, args.nguess_keep, args.cluster_gap_factor, args.yscale, args.infile)
2274
2275 elif args.mode == "fit":
2276 _, omega, int_target = read_data(args.infile, substrate, film, energy)
2277 if args.method == "random":
2278 fit_random(substrate, film, energy, omega, int_target, param_sets, parameter_file, args.yscale, args.residual_scale)
2279 elif args.method == "pso":
2280 fit_pso(substrate, film, energy, omega, int_target, param_sets, parameter_file, args.nmaxiter, args.tol, args.pso_nparticles, args.pso_w, args.pso_c1, args.pso_c2, args.yscale, args.residual_scale, args.pso_stall_max, args.pso_spread_rtol)
2281 else:
2282 fit_scipy(substrate, film, energy, omega, int_target, param_sets, parameter_file, args.method, args.nmaxiter, args.tol, args.yscale, args.residual_scale)
2283
2284if __name__ == "__main__":
2285 parser = build_parser()
2286 try:
2287 main(initialize(parser))
2288 except Exception:
2289 traceback.print_exc()
2290 sys.exit(1)