バックトレース (data.trace) — 実験的

data.trace は、6D 位相空間グリッドから粒子を作り、到達確率、backward trace、forward trace をまとめて扱う workflow API です。新しい解析コードでは data.trace.forward() / data.trace.backward() / data.trace.both() を使ってください。

従来の data.backtrace は、単一粒子や任意の Particle 配列を直接渡したい既存コード向けの低水準 API として残っています。このページでは本線を data.trace にし、data.backtrace の経路は折りたたみ内にまとめています。

要件: バックトレースは外部の vdist-solver-fortran パッケージ (vdsolverf) に依存します。pip install vdist-solver-fortran でインストールしてください。未インストールの環境では data.trace.* の呼び出し時に ImportError が出ます。

いつ使うか

  • ある観測点に到達する粒子の 位相空間分布 を見たい

  • 到達粒子を backward / forward の軌跡 として追いたい

  • 到達粒子の エネルギースペクトル を描きたい

  • 確率と軌跡を同じ粒子集合から計算して、確率を alpha に使いたい

バックトレースは保存済みフィールドを使って ODE を解くため、大きな max_step や細かい位相空間グリッドでは時間がかかります。HPC ノードに処理を任せたい場合は、リモート実行と組み合わせてください(後述)。

単位に注意: data.trace の入力は EMSES シミュレーション単位です。SI 値から指定する場合は data.unit で変換してから渡します。

クイックスタート

import emout

data = emout.Emout("output_dir")

vx_scan = (data.unit.v.trans(-3e5), data.unit.v.trans(3e5), 64)
vz_scan = (data.unit.v.trans(-3e5), data.unit.v.trans(3e5), 64)

trace = data.trace.forward(
    x=20.0, y=32.0, z=40.0,
    vx=vx_scan,
    vy=0.0,
    vz=vz_scan,
    ispec=0,
    get_trace=True,
    get_probabilities=True,
    max_step=10000,
    n_threads=8,
)

trace.plot("vx", "vz", cmap="viridis")       # 到達確率 heatmap
trace.plot_traces("x", "z")                  # 確率 alpha 付き軌跡
trace.probabilities.plot_energy_spectrum(scale="log")

スカラー値だけを渡すと、位相空間グリッドのサイズ 1 軸として扱われます。確率計算なしで 1 本の軌跡だけ欲しい場合も data.trace で書けます:

single = data.trace.backward(
    x=20.0, y=32.0, z=40.0,
    vx=data.unit.v.trans(1.0e5),
    vy=0.0,
    vz=data.unit.v.trans(-2.0e5),
    ispec=0,
    get_trace=True,
    get_probabilities=False,
)

single.plot_traces("t", "x")
single.traces.xvz.plot()

入力単位の約束

data.trace に渡す x / y / z / vx / vy / vzdtprobability_dtすべて EMSES シミュレーション単位です。emout は SI から自動変換しません。

SI 値から指定したい場合は、呼び出し前に data.unit で EMSES 単位へ変換してください:

position = (
    data.unit.length.trans(0.20),  # m -> EMSES length
    data.unit.length.trans(0.32),
    data.unit.length.trans(0.40),
)
vx_scan = (
    data.unit.v.trans(-3.0e5),     # m/s -> EMSES velocity
    data.unit.v.trans(3.0e5),
    64,
)
vz_scan = (
    data.unit.v.trans(-3.0e5),
    data.unit.v.trans(3.0e5),
    64,
)

data.unitplasma.inp!!key dx=...,to_c=... ヘッダー(または plasma.toml[meta.unit_conversion])がある場合にだけ利用できます。単位変換 metadata がない場合は、EMSES 単位の値を直接渡してください。

TraceResult.phasesTraceResult.particlestrace.traces.positions_list などの配列も EMSES 単位のまま保持されます。一方で、trace.plot() / trace.plot_traces() / trace.traces.xz.plot() は unit metadata がある場合に既定で SI 単位へ変換して表示します(use_si=False で EMSES 単位表示)。

dtprobability_dt は EMSES 時間単位の非負の刻み幅です。None の場合は abs(data.inp.dt) を使います。data.trace.backward() は solver に +dtdata.trace.forward() は内部で符号を反転して -dt を渡します。data.trace.both() は同じ dt から backward に +dt、forward に -dt を使います。到達確率計算は probability_dt を backward 側の符号で使います。負の値を渡すと ValueError になります。

Workflow API: data.trace

data.trace.backward() / data.trace.forward() / data.trace.both() は常に TraceResult を返します。要求しなかった payload は None です。

確率と軌跡をまとめて得る

trace = data.trace.forward(
    x=20.0, y=32.0, z=40.0,
    vx=vx_scan,
    vy=0.0,
    vz=vz_scan,
    get_trace=True,
    get_probabilities=True,
)

trace.probabilities        # ProbabilityResult
trace.forward_traces       # MultiBacktraceResult
trace.backward_traces      # None
trace.alpha                # np.clip(trace.probabilities.probabilities, 0, 1)

trace.plot("vx", "vz")     # 到達確率 heatmap
trace.plot_traces("x", "z")

trace.plot() は確率がある場合は trace.probabilities.pair(...).plot()、確率がない場合は trace.plot_traces() に自動で振り分けます。明示したい場合は kind="probability" / kind="trace" を渡します。

軌跡だけを得る

get_probabilities=False にすると、確率計算を省略して phase-space grid から粒子だけを作り、軌跡だけを得ます。この場合 trace.probabilitiestrace.alphaNone で、plot_traces() は一様な alpha で描画します。

trace = data.trace.forward(
    x=20.0, y=32.0, z=40.0,
    vx=vx_scan,
    vy=0.0,
    vz=vz_scan,
    get_trace=True,
    get_probabilities=False,
)

trace.forward_traces.xz.plot(alpha=0.3)
trace.plot_traces("x", "z", alpha=0.3)

backward と forward を同時に得る

both() は同じ phase-space grid から backward / forward の両方を計算します。確率も要求した場合、確率計算は 1 回だけです。

trace = data.trace.both(..., get_trace=True)
trace.backward_traces.xz.plot(alpha=trace.alpha)
trace.forward_traces.xz.plot(alpha=trace.alpha)
trace.plot_traces("x", "z", direction="backward")
trace.plot_traces("x", "z", direction="forward")

3D に重ねる

plot3d() は PyVista plotter を返します。既存の 3D field / boundary 表示に重ねたい場合は、その plotter を渡します。

plotter = data.phisp[-1].plot3d(mode="slice", show=False)
trace.plot3d(plotter=plotter, direction="forward", tube_radius=0.05, show=True)

結果の描画

2D ヒートマップへの射影

trace.probabilities.pair(var1, var2) は選ばれていない 4 軸を台形則で積分し、HeatmapData を返します。trace.plot(var1, var2) はこの操作を短く書く入口です。

trace.plot("vx", "vz", cmap="viridis")
trace.probabilities.xvx.plot()
trace.probabilities.yz.plot(cmap="plasma")

HeatmapData.plot() はカラーバー付きの pcolormesh を描き、軸ラベルには SI 単位が付きます(use_si=False でグリッド単位に戻せます)。追加のキーワード引数はすべて pcolormesh にそのまま渡されるので、vmin / vmaxnorm=LogNorm(...) で配色を制御できます。

エネルギースペクトル

エネルギースペクトルは確率 payload 側の ProbabilityResult から描きます。

trace.probabilities.plot_energy_spectrum(scale="log", energy_bins=80)
hist, bin_edges = trace.probabilities.energy_spectrum(energy_bins=80)

内部で plasma.inpwp や光電流設定から基準数密度 n0 を計算し、各位相点の確率に掛けてから積分しています(nflag_emit == 2 の光電子は pathcurf から導出)。

リモート実行との連携

data.traceEmout ファサード経由で remote_open_kwargs を持ち、emout server が動いていれば既定でワーカー上で実行されます(結果は RemoteTraceResult プロキシとして返ります)。ワーカー上にキャッシュされているので、プロットパラメータを変えても再計算は走りません。

from emout.distributed import remote_figure

trace = data.trace.forward(
    x=20.0, y=32.0, z=40.0,
    vx=vx_scan,
    vy=0.0,
    vz=vz_scan,
    get_trace=True,
)

with remote_figure():
    trace.plot("vx", "vz", cmap="viridis")

with remote_figure():
    trace.plot_traces("x", "z")

trace.drop()   # 不要になったらワーカーメモリを解放

明示的な remote 記法を使いたいときは data.remote().trace... に切り替えます:

from emout.distributed import remote_scope, remote_figure

with remote_scope():
    rdata = data.remote()
    trace = rdata.trace.forward(
        x=20.0, y=32.0, z=40.0,
        vx=vx_scan,
        vy=0.0,
        vz=vz_scan,
        get_trace=True,
    )

    with remote_figure():
        trace.plot("vx", "vz")
        trace.plot_traces("x", "z")

リモート実行全体の仕組み・環境変数・サーバー起動については リモート実行ガイド を参照してください。

fetch() でローカルに取り出す

matplotlib で細かくカスタマイズしたい(独自アノテーション、共通カラーバー、subplot への差し込み、など)場合は、fetch() で結果を小さなローカルオブジェクトに変換できます:

local_trace = trace.fetch()
heatmap = local_trace.probabilities.vxvz
fig, ax = plt.subplots()
heatmap.plot(ax=ax, cmap="plasma")
ax.axhline(y=0, color="red", linestyle="--")
既存コード向け: 低水準 API `data.backtrace` を表示

低水準 API: data.backtrace

data.backtrace は、data.trace が内部で使っている BacktraceWrapper です。1 本の軌跡を明示的な position / velocity で計算したい場合、vdsolverf.core.Particle を直接渡したい場合、または solver に渡す dt の符号を直接制御したい場合に使います。新しい位相空間 workflow は data.trace を優先してください。

単一粒子: get_backtrace

position = (20.0, 32.0, 40.0)
velocity = (
    data.unit.v.trans(1.0e5),
    0.0,
    data.unit.v.trans(-2.0e5),
)

bt = data.backtrace.get_backtrace(position, velocity, ispec=0, max_step=50000)

bt.tx.plot()                 # = bt.pair("t", "x")
bt.xvz.plot()                # = bt.pair("x", "vz")
bt.yz.plot(color="black")    # 軌道の yz 投影

bt.tsbt.probabilitybt.positionsbt.velocities は EMSES 単位の配列です。XYData.plot() はデフォルトで SI 単位に変換し、軸ラベルも自動生成します(use_si=False で EMSES 単位のまま表示できます)。

複数粒子: get_backtraces

import numpy as np

positions = np.array([[20, 32, 40], [21, 32, 40], [22, 32, 40]], dtype=float)
velocities = np.zeros_like(positions)
velocities[:, 0] = data.unit.v.trans(1.0e5)

many = data.backtrace.get_backtraces(positions, velocities, ispec=0)
many.xz.plot(alpha=0.5)
many.sample(50, random_state=0).tvx.plot()

positionsvelocities は対応する粒子ごとの (N, 3) 配列です。Cartesian product の位相空間グリッドを作りたい場合は data.trace を使ってください。

Particle オブジェクトを直接渡す

from vdsolverf.core import Particle

particles = [Particle(p, v) for p, v in zip(positions, velocities)]
many = data.backtrace.get_backtraces_from_particles(particles, ispec=0)

ProbabilityResult から得られた粒子リストをそのまま渡して、確率が高い粒子だけ軌跡を描く、といった連結もできます:

result = data.backtrace.get_probabilities(...)
bt = data.backtrace.get_backtraces_from_particles(result.particles, ispec=0)
bt.xz.plot(alpha=np.clip(result.probabilities, 0, 1))

到達確率: get_probabilities

data.trace は内部で data.backtrace.get_probabilities(...) を呼び、ProbabilityResulttrace.probabilities として保持します。低水準 API を直接使う場合は次の形です:

result = data.backtrace.get_probabilities(
    x=20.0, y=32.0, z=40.0,
    vx=vx_scan,
    vy=0.0,
    vz=vz_scan,
    ispec=0,
    max_step=10000,
    n_threads=8,
)

result.vxvz.plot(cmap="viridis")
result.plot_energy_spectrum(scale="log")

MPI backend

parallel="mpi" / parallel="srun" は低水準 get_probabilities() の backend オプションです。data.trace にも **kwargs として渡せます。

trace = data.trace.forward(
    x=20.0, y=32.0, z=40.0,
    vx=vx_scan,
    vy=0.0,
    vz=vz_scan,
    max_step=10000,
    parallel="srun",
    ntasks=8,
    n_threads=2,
    cpus_per_task=2,
)

関連クラス

詳細なシグネチャは API リファレンス(emout.core.backtrace パッケージ)を参照してください。

  • TraceWrapperdata.trace の実体

  • TraceResult — 確率・軌跡 payload をまとめる workflow 結果

  • BacktraceWrapperdata.backtrace の実体(低水準 API)

  • BacktraceResult / MultiBacktraceResult — 軌跡コンテナ

  • ProbabilityResult — 6D 確率グリッドとヒートマップ射影

  • XYData / MultiXYData / HeatmapData — 可視化用の軽量コンテナ