0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

SS 433には約13日のbinary orbital motionと約162日のprecessionがあり、観測時のgeometryは時間とともに変化しています。

以前、ChandraによるSS 433観測について、

(\phi_{\rm orb},\,\phi_{\rm prec})

から観測時のgeometryをポンチ絵として並べてみました。

今回はその XRISM版 です。

XRISMではSS 433を長時間観測しているため、1回の観測中でもbinaryの配置が少しずつ変化していきます。

今回は、公開済みの2024年PV観測と2025年AO1観測について、Sakai et al. (2026)で使用した時間区間に対応するgeometryを並べてみます。

今回見るXRISM観測

今回扱うのはこちらの2観測です。

observation ObsID observation date
PV 300041010 2024-04-10 – 2024-04-15
AO1 201014010 2025-03-26 – 2025-03-28

Sakai et al. (2026)では、時間変動を見るために、

  • 2024 PV:30区間
  • 2025 AO1:5区間

に分けて解析しています。

今回は、その 30 + 5区間に対応するgeometry を35枚のポンチ絵として並べてみます。

XRISMが見ていたgeometryを並べる

こちらが結果です。

ss433_xrism_pv_ao1_overview_5x7.png

上6行が 2024 PV、下1行が 2025 AO1 です。

各panelは、Sakai et al. (2026)で使用した時間区間に対応しています。

黄色がcompanion、黒がcompact object、緑がdiskです。

青と赤はそれぞれ LOS-approachingLOS-receding を示しています。

phaseの数字だけを見るよりも、binary / disk / jetをまとめて描いてしまうと、

この時間帯にXRISMはどんな配置を見ていたのか

が少し分かりやすくなります。

PV観測はeclipse内外を見ると面白い

個人的に今回の図で分かりやすいのが、2024年のPV観測です。

左上から時間順に眺めていくと、binary orbital motionによってcompanionとcompact objectの位置関係が少しずつ変化していきます。

PV観測ではeclipse内外を観測しているので、

eclipse中はどんな配置なのか
out-of-eclipseになるとどう変わるのか

を一枚の図で眺めることができます。

SS 433はphaseの数字だけ見ていると少し頭の中で組み立てるのが大変なので、こうして絵にしてしまうとかなり楽になると思います。

phaseのephemerisについて

今回のphaseは、Sakai et al. (2026)で採用したephemerisを使っています。

precessionについては、従来のkinematic modelをベースに、XRISM/Resolveで測定したjetのDoppler shiftからreference epochを更新したephemerisを使用しています。

orbital phaseについては、Cherepashchuk et al. (2023)によるperiod derivative $\dot{P}_{\rm orb}$ を含むephemerisを使用しています。

SS 433のephemerisについては取り方によって少し違いもあり、このあたり自体にもいろいろ議論があります。

今回はephemerisそのものを検討することが目的ではないので、詳しくはSakai et al. (2026)を参照してください。

今回の図は、あくまでXRISM観測時のgeometryを直感的に確認するための模式図です。

Sakai et al. (2026)の各時間区間との対応を見るために作成していますが、精密なeclipse計算やradiative transferなどを行ったscience解析用modelではありません。

研究上の定量解析に使用するものではなく、あくまで観測geometryを眺めるための補助図くらいに見てもらえればと思います。

XRISMの結果と一緒に眺める

今回の図は単純なポンチ絵ですが、XRISMによるSS 433の結果と一緒に眺めると少し面白くなります。

例えばShidatsu et al. (2025)では、2024年PV観測のeclipse内外を利用して、jet emission lineのvelocity dispersionについて議論されています。

Sakai et al. (2026)では、ResolveによるDoppler shift測定からjet velocityの時間変動やnutationとの関係も調べています。

また、Takagi et al. (2026)ではneutral Fe / Ni emission lineも詳しく調べられています。

こうした成分や時間変動がorbital phaseや観測geometryとどのように関係しているのかも気になるところです。

まとめ

今回は、公開済みのXRISMによるSS 433のPV / AO1観測について、Sakai et al. (2026)の時間分割に対応するgeometryを35枚のポンチ絵として並べてみました。

特に2024年PV観測では、eclipse内外でbinaryの配置が変化していく様子が一枚の図で見えるので、観測のイメージがかなりつかみやすくなった気がします。

XRISMのSS 433観測が

実際にどんな配置を見ていたのか

を簡単に確認するには、こうして時間順に並べてみるのも便利かもしれません。

参考文献

関連記事

コード

今回使用したコードはこちらです。

コードはこちら
"""Create a public SS 433 XRISM PV/AO1 phase-geometry overview.

The orbital and precession phases are direct inputs.  No observation dates or
ephemerides are used to derive them.  The only outputs are one 5 x 7 vector
PDF and one matching PNG preview.
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D
from matplotlib.patches import Circle, Patch, Polygon


# =============================================================================
# Physical parameters
# =============================================================================

P_ORB = 13.08211  # d; used only to derive physical binary scale and velocity
M_COMPACT = 6.0  # solar masses; representative
M_COMPANION = 12.0  # solar masses; representative
ORBIT_SENSE = +1

PREC_CONE_DEG = 20.92
INCLINATION_DEG = 78.05
POSITION_ANGLE_DEG = 98.2
PREC_SENSE = -1  # retrograde relative to +L_orb
BETA = 0.2647
DISK_RADIUS_RSUN = 8.0

G_SI = 6.67430e-11
M_SUN_KG = 1.98847e30
R_SUN_M = 6.957e8
AU_M = 1.495978707e11
DAY_S = 86400.0
C_M_S = 299792458.0
C_AU_DAY = C_M_S * DAY_S / AU_M

PREC_CONE_RAD = np.deg2rad(PREC_CONE_DEG)
INCLINATION_RAD = np.deg2rad(INCLINATION_DEG)
POSITION_ANGLE_RAD = np.deg2rad(POSITION_ANGLE_DEG)

M_TOTAL = M_COMPACT + M_COMPANION
P_ORB_S = P_ORB * DAY_S
A_REL_M = (
    G_SI * M_TOTAL * M_SUN_KG * (P_ORB_S / (2.0 * np.pi)) ** 2
) ** (1.0 / 3.0)
A_REL_AU = A_REL_M / AU_M
Q_DONOR_OVER_COMPACT = M_COMPANION / M_COMPACT


def eggleton_roche_fraction(q_donor_over_compact: float) -> float:
    """Donor volume-equivalent Roche radius divided by separation."""
    q23 = q_donor_over_compact ** (2.0 / 3.0)
    return 0.49 * q23 / (
        0.6 * q23 + np.log(1.0 + q_donor_over_compact ** (1.0 / 3.0))
    )


COMPANION_RADIUS_AU = A_REL_AU * eggleton_roche_fraction(
    Q_DONOR_OVER_COMPACT
)
DISK_RADIUS_AU = DISK_RADIUS_RSUN * R_SUN_M / AU_M
COMPACT_ORBIT_RADIUS_AU = A_REL_AU * M_COMPANION / M_TOTAL
COMPANION_ORBIT_RADIUS_AU = A_REL_AU * M_COMPACT / M_TOTAL
V_JET_AU_DAY = BETA * C_AU_DAY

# Binary frame -> observer frame: (LOS toward Earth, North, East).
R_BIN_TO_OBS = np.array(
    [
        [np.sin(INCLINATION_RAD), 0.0, np.cos(INCLINATION_RAD)],
        [
            -np.cos(INCLINATION_RAD) * np.cos(POSITION_ANGLE_RAD),
            np.sin(POSITION_ANGLE_RAD),
            np.sin(INCLINATION_RAD) * np.cos(POSITION_ANGLE_RAD),
        ],
        [
            -np.cos(INCLINATION_RAD) * np.sin(POSITION_ANGLE_RAD),
            -np.cos(POSITION_ANGLE_RAD),
            np.sin(INCLINATION_RAD) * np.sin(POSITION_ANGLE_RAD),
        ],
    ]
)


# =============================================================================
# Fixed visualization parameters
# =============================================================================

BLUE = "#1479d1"
RED = "#d4483b"
STAR_COLOR = "#f2b134"
STAR_EDGE = "#8a5a00"
DISK_COLOR = "#159a70"
DISK_DARK = "#086f51"
COMPACT_COLOR = "#171717"
ORBIT_COLOR = "#6b7280"

LOS_APPROACHING_LABEL = "LOS-approaching"
LOS_RECEDING_LABEL = "LOS-receding"

DISK_OPACITY = 1.0
JET_GUIDE_LENGTH_AU = 0.22
COMPACT_MARKER_RADIUS_AU = 0.009  # symbolic, not the black-hole radius
OBSERVER_LIMIT_AU = 0.36


# =============================================================================
# Public XRISM phase ranges; every segment is a bin center
# =============================================================================

@dataclass(frozen=True)
class PhaseBlock:
    year: int
    name: str
    n_segments: int
    phi_prec_start: float
    phi_prec_end: float
    phi_orb_start: float
    phi_orb_end_unwrapped: float


@dataclass(frozen=True)
class Segment:
    block: PhaseBlock
    segment_id: int
    phi_prec: float
    phi_orb: float
    phi_orb_unwrapped: float


PHASE_BLOCKS = (
    PhaseBlock(2024, "PV", 30, 0.152, 0.182, 0.965, 1.330),
    PhaseBlock(2025, "AO1", 5, 0.311, 0.325, 0.717, 0.893),
)


def segments_for_block(block: PhaseBlock) -> list[Segment]:
    segments: list[Segment] = []
    for segment_id in range(block.n_segments):
        f = (segment_id + 0.5) / block.n_segments
        phi_prec = block.phi_prec_start + f * (
            block.phi_prec_end - block.phi_prec_start
        )
        phi_orb_unwrapped = block.phi_orb_start + f * (
            block.phi_orb_end_unwrapped - block.phi_orb_start
        )
        segments.append(
            Segment(
                block=block,
                segment_id=segment_id,
                phi_prec=float(phi_prec),
                phi_orb=float(phi_orb_unwrapped % 1.0),
                phi_orb_unwrapped=float(phi_orb_unwrapped),
            )
        )
    return segments


def all_segments() -> list[Segment]:
    return [
        segment
        for block in PHASE_BLOCKS
        for segment in segments_for_block(block)
    ]


# =============================================================================
# Direct phase -> geometry functions; nutation is intentionally absent
# =============================================================================

def _as_xyz(x, y, z):
    return np.stack([x, y, z], axis=-1)


def transform_to_observer(vectors):
    """Binary (Xb, Yb, Zb) -> (LOS toward Earth, North, East)."""
    vectors = np.asarray(vectors, dtype=float)
    return vectors @ R_BIN_TO_OBS.T


def binary_state_from_orbital_phase(phi_orb):
    """Barycentric positions and velocities at direct orbital-phase input."""
    phi_orb = np.asarray(phi_orb, dtype=float)
    phase = ORBIT_SENSE * 2.0 * np.pi * phi_orb + np.pi
    omega = ORBIT_SENSE * 2.0 * np.pi / P_ORB
    radial = _as_xyz(
        np.cos(phase), np.sin(phase), np.zeros_like(phase)
    )
    tangent = omega * _as_xyz(
        -np.sin(phase), np.cos(phase), np.zeros_like(phase)
    )
    r_compact = COMPACT_ORBIT_RADIUS_AU * radial
    r_companion = -COMPANION_ORBIT_RADIUS_AU * radial
    v_compact = COMPACT_ORBIT_RADIUS_AU * tangent
    v_companion = -COMPANION_ORBIT_RADIUS_AU * tangent
    return r_companion, r_compact, v_companion, v_compact


def jet_direction_from_precession_phase(phi_prec):
    """Unit plus-jet direction using the retrograde convention."""
    phi_prec = np.asarray(phi_prec, dtype=float)
    phi_angle = PREC_SENSE * 2.0 * np.pi * phi_prec
    return _as_xyz(
        np.sin(PREC_CONE_RAD) * np.cos(phi_angle),
        np.sin(PREC_CONE_RAD) * np.sin(phi_angle),
        np.cos(PREC_CONE_RAD) * np.ones_like(phi_angle),
    )


def relativistic_velocity_addition(source_velocity, velocity_source_frame):
    """Lorentz-add compact-object orbital motion to a jet velocity."""
    u = np.asarray(source_velocity, dtype=float)
    vp = np.asarray(velocity_source_frame, dtype=float)
    u, vp = np.broadcast_arrays(u, vp)
    umag = np.linalg.norm(u, axis=-1)
    uhat = u / umag[..., None]
    vpar_scalar = np.sum(vp * uhat, axis=-1)
    vpar = vpar_scalar[..., None] * uhat
    vperp = vp - vpar
    gamma_u = 1.0 / np.sqrt(1.0 - (umag / C_AU_DAY) ** 2)
    denominator = 1.0 + np.sum(u * vp, axis=-1) / C_AU_DAY**2
    return (
        ((vpar_scalar + umag) / denominator)[..., None] * uhat
        + vperp / (gamma_u * denominator)[..., None]
    )


def instantaneous_los_branches(phi_orb, phi_prec):
    """Return jet directions classified by instantaneous barycentric LOS state.

    Blue is assigned to the branch with the more Earth-directed LOS velocity
    at the supplied phases.  The colors are not permanent physical-branch
    names.
    """
    n_plus = jet_direction_from_precession_phase(phi_prec)
    _, _, _, v_source = binary_state_from_orbital_phase(phi_orb)
    v_plus = relativistic_velocity_addition(v_source, V_JET_AU_DAY * n_plus)
    v_minus = relativistic_velocity_addition(v_source, -V_JET_AU_DAY * n_plus)
    plus_los = transform_to_observer(v_plus)[0]
    minus_los = transform_to_observer(v_minus)[0]
    if plus_los >= minus_los:
        return n_plus, -n_plus
    return -n_plus, n_plus


def orthonormal_disk_basis(normal):
    normal = np.asarray(normal, dtype=float)
    normal = normal / np.linalg.norm(normal)
    reference = np.array([0.0, 0.0, 1.0])
    if abs(np.dot(normal, reference)) > 0.90:
        reference = np.array([1.0, 0.0, 0.0])
    u = np.cross(normal, reference)
    u /= np.linalg.norm(u)
    v = np.cross(normal, u)
    return u, v


def disk_circle(center, normal, radius=DISK_RADIUS_AU, n=160):
    u, v = orthonormal_disk_basis(normal)
    angle = np.linspace(0.0, 2.0 * np.pi, n)
    return (
        np.asarray(center)
        + radius * np.cos(angle)[:, None] * u
        + radius * np.sin(angle)[:, None] * v
    )


def masked_curve(points_xy, mask):
    points_xy = np.asarray(points_xy, dtype=float)
    mask = np.asarray(mask, dtype=bool)
    out = points_xy.copy()
    out[~mask] = np.nan
    return out


def observer_binary_geometry_from_phases(phi_orb, phi_prec):
    """Return projected central geometry and LOS depth ordering."""
    r_star, r_compact, _, _ = binary_state_from_orbital_phase(phi_orb)
    normal = jet_direction_from_precession_phase(phi_prec)
    star_obs = transform_to_observer(r_star)
    compact_obs = transform_to_observer(r_compact)
    return {
        "star_obs": star_obs,
        "compact_obs": compact_obs,
        "normal": normal,
        "compact_in_front": bool((compact_obs - star_obs)[0] > 0.0),
    }


def orbit_curves_from_phase(n=400):
    phases = np.linspace(0.0, 1.0, n)
    r_star, r_compact, _, _ = binary_state_from_orbital_phase(phases)
    return r_star, r_compact


# =============================================================================
# Shared fixed-scale observer-side drawing routine
# =============================================================================

def draw_observer_binary_phase(ax, phi_orb: float, phi_prec: float) -> None:
    r_star_orbit, r_compact_orbit = orbit_curves_from_phase()
    star_orbit_obs = transform_to_observer(r_star_orbit)
    compact_orbit_obs = transform_to_observer(r_compact_orbit)
    ax.plot(
        star_orbit_obs[:, 2], star_orbit_obs[:, 1],
        color=STAR_COLOR, lw=0.90, alpha=0.48, zorder=1,
    )
    ax.plot(
        compact_orbit_obs[:, 2], compact_orbit_obs[:, 1],
        color=ORBIT_COLOR, lw=0.90, alpha=0.62, zorder=1,
    )
    ax.scatter(
        0.0, 0.0, marker="+", s=16, color="black",
        linewidths=0.75, zorder=1,
    )

    view = observer_binary_geometry_from_phases(phi_orb, phi_prec)
    star_obs = view["star_obs"]
    compact_obs = view["compact_obs"]
    star_xy = np.array([star_obs[2], star_obs[1]])
    compact_xy = np.array([compact_obs[2], compact_obs[1]])
    ax.plot(
        [star_xy[0], compact_xy[0]], [star_xy[1], compact_xy[1]],
        color=ORBIT_COLOR, lw=0.65, alpha=0.50, zorder=1,
    )

    disk = disk_circle(
        binary_state_from_orbital_phase(phi_orb)[1], view["normal"]
    )
    disk_obs = transform_to_observer(disk)
    disk_xy = np.c_[disk_obs[:, 2], disk_obs[:, 1]]
    near_mask = disk_obs[:, 0] >= compact_obs[0]
    near_xy = masked_curve(disk_xy, near_mask)
    far_xy = masked_curve(disk_xy, ~near_mask)

    n_app, n_rec = instantaneous_los_branches(phi_orb, phi_prec)
    n_app_obs = transform_to_observer(n_app)
    n_rec_obs = transform_to_observer(n_rec)
    app_end_obs = compact_obs + JET_GUIDE_LENGTH_AU * n_app_obs
    rec_end_obs = compact_obs + JET_GUIDE_LENGTH_AU * n_rec_obs

    star_patch = Circle(
        star_xy, COMPANION_RADIUS_AU,
        facecolor=STAR_COLOR, edgecolor=STAR_EDGE,
        linewidth=0.85, alpha=1.0,
    )
    compact_patch = Circle(
        compact_xy, COMPACT_MARKER_RADIUS_AU,
        facecolor=COMPACT_COLOR, edgecolor="white", linewidth=0.55,
    )
    disk_patch = Polygon(
        disk_xy, closed=True, facecolor=DISK_COLOR, edgecolor=DISK_DARK,
        linewidth=0.75, alpha=DISK_OPACITY,
    )
    ax.add_patch(star_patch)
    rec_line, = ax.plot(
        [compact_obs[2], rec_end_obs[2]],
        [compact_obs[1], rec_end_obs[1]],
        color=RED, lw=1.65, solid_capstyle="round",
    )
    ax.add_patch(disk_patch)
    far_line, = ax.plot(
        far_xy[:, 0], far_xy[:, 1],
        color=DISK_DARK, lw=0.8, ls="--",
    )
    near_line, = ax.plot(
        near_xy[:, 0], near_xy[:, 1], color="#33d19a", lw=1.55,
    )
    app_line, = ax.plot(
        [compact_obs[2], app_end_obs[2]],
        [compact_obs[1], app_end_obs[1]],
        color=BLUE, lw=1.65, solid_capstyle="round",
    )
    ax.add_patch(compact_patch)

    # Painter ordering follows the exact observer-side depth relationship.
    if view["compact_in_front"]:
        star_patch.set_zorder(3)
        rec_line.set_zorder(4)
        disk_patch.set_zorder(5)
        far_line.set_zorder(5)
        near_line.set_zorder(6)
        app_line.set_zorder(7)
        compact_patch.set_zorder(8)
    else:
        rec_line.set_zorder(2)
        disk_patch.set_zorder(3)
        far_line.set_zorder(3)
        near_line.set_zorder(4)
        app_line.set_zorder(4)
        compact_patch.set_zorder(4)
        star_patch.set_zorder(9)

    ax.set_xlim(OBSERVER_LIMIT_AU, -OBSERVER_LIMIT_AU)  # east is left
    ax.set_ylim(-OBSERVER_LIMIT_AU, OBSERVER_LIMIT_AU)
    ax.set_aspect("equal", adjustable="box")
    ax.set_facecolor("white")
    ax.set_xticks([])
    ax.set_yticks([])
    for spine in ax.spines.values():
        spine.set_color("#d1d5db")
        spine.set_linewidth(0.55)


def legend_handles():
    return [
        Patch(facecolor=STAR_COLOR, edgecolor=STAR_EDGE, label="Companion"),
        Line2D(
            [0], [0], marker="o", color="none",
            markerfacecolor=COMPACT_COLOR, markeredgecolor="white",
            label="Compact object (symbolic marker)",
        ),
        Patch(facecolor=DISK_COLOR, edgecolor=DISK_DARK, label="Opaque disk"),
        Line2D([0], [0], color=BLUE, lw=2, label=LOS_APPROACHING_LABEL),
        Line2D([0], [0], color=RED, lw=2, label=LOS_RECEDING_LABEL),
    ]


# =============================================================================
# Single public overview output
# =============================================================================

def make_overview(
    segments: list[Segment], pdf_path: Path, png_path: Path, png_dpi: int
) -> None:
    fig, axes = plt.subplots(7, 5, figsize=(13.0, 17.3))
    for ax, segment in zip(axes.flat, segments):
        draw_observer_binary_phase(ax, segment.phi_orb, segment.phi_prec)

        ax.text(
            0.025, 0.975,
            f"{segment.block.name} {segment.segment_id:02d}",
            transform=ax.transAxes,
            ha="left", va="top",
            fontsize=7.2,
            weight="bold",
            bbox={
                "facecolor": "white", "alpha": 0.88,
                "edgecolor": "none", "pad": 1.3,
            },
            zorder=20,
        )
        ax.text(
            0.025, 0.912,
            rf"$\phi_{{\rm prec}}$ = {segment.phi_prec:.3f}" "\n"
            rf"$\phi_{{\rm orb}}$  = {segment.phi_orb:.3f}",
            transform=ax.transAxes,
            ha="left", va="top",
            fontsize=5.8,
            linespacing=1.10,
            bbox={
                "facecolor": "white", "alpha": 0.88,
                "edgecolor": "none", "pad": 1.1,
            },
            zorder=20,
        )

    fig.suptitle(
        "SS 433 XRISM phase geometry - PV and AO1",
        fontsize=16, y=0.992,
    )
    fig.legend(
        handles=legend_handles(), loc="lower center", ncol=5, fontsize=8.2,
        bbox_to_anchor=(0.5, 0.023), frameon=False,
    )
    fig.subplots_adjust(
        left=0.040, right=0.995, top=0.970, bottom=0.057,
        wspace=0.035, hspace=0.075,
    )

    block_ranges = ((0, 5, "2024 PV"), (6, 6, "2025 AO1"))
    for first_row, last_row, text in block_ranges:
        top = axes[first_row, 0].get_position().y1
        bottom = axes[last_row, 0].get_position().y0
        fig.text(
            0.011, 0.5 * (top + bottom), text,
            rotation=90, ha="center", va="center",
            fontsize=11, weight="bold",
        )

    separator_y = 0.5 * (
        axes[5, 0].get_position().y0 + axes[6, 0].get_position().y1
    )
    fig.add_artist(
        Line2D(
            [0.031, 0.995], [separator_y, separator_y],
            transform=fig.transFigure, color="#9ca3af", lw=0.8,
        )
    )
    fig.text(
        0.50, 0.011,
        "Every panel: fixed +/-0.36 AU field; east is left; colors show instantaneous LOS state",
        ha="center", va="bottom", fontsize=8.5, color="#4b5563",
    )

    pdf_path.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(
        pdf_path, format="pdf",
        metadata={"Title": "SS 433 XRISM PV and AO1 phase geometry"},
    )
    fig.savefig(png_path, dpi=png_dpi, facecolor="white")
    plt.close(fig)


def validate_segments(segments: list[Segment]) -> None:
    assert len(PHASE_BLOCKS) == 2
    assert [block.n_segments for block in PHASE_BLOCKS] == [30, 5]
    assert len(segments) == 35
    assert [segment.segment_id for segment in segments[:30]] == list(range(30))
    assert [segment.segment_id for segment in segments[30:]] == list(range(5))
    assert all(0.0 <= segment.phi_orb < 1.0 for segment in segments)
    pv = segments[:30]
    wrap_ids = [
        index
        for index in range(1, len(pv))
        if pv[index].phi_orb < pv[index - 1].phi_orb
    ]
    assert wrap_ids == [3]


def print_phase_table(segments: list[Segment]) -> None:
    for segment in segments:
        print(
            f"{segment.block.year} {segment.block.name} "
            f"ID={segment.segment_id:02d} "
            f"phi_prec={segment.phi_prec:.6f} "
            f"phi_orb={segment.phi_orb:.6f}"
        )


def main() -> None:
    default_output_dir = Path(__file__).resolve().parent
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--output-dir", type=Path, default=default_output_dir,
        help="directory for the PDF and PNG outputs",
    )
    parser.add_argument(
        "--png-dpi", type=int, default=180,
        help="resolution of the PNG preview",
    )
    args = parser.parse_args()

    plt.rcParams.update(
        {
            "font.family": "DejaVu Sans",
            "pdf.fonttype": 42,
            "ps.fonttype": 42,
            "axes.linewidth": 0.7,
            "savefig.facecolor": "white",
        }
    )

    segments = all_segments()
    validate_segments(segments)
    print_phase_table(segments)

    pdf_path = args.output_dir / "ss433_xrism_pv_ao1_overview_5x7.pdf"
    png_path = args.output_dir / "ss433_xrism_pv_ao1_overview_5x7.png"
    make_overview(segments, pdf_path, png_path, args.png_dpi)

    assert pdf_path.is_file() and pdf_path.stat().st_size > 1000
    assert png_path.is_file() and png_path.stat().st_size > 1000
    print("PV panels   = 30")
    print("AO1 panels  = 5")
    print("total grid  = 35 cells (5 x 7)")
    print(f"PDF: {pdf_path}")
    print(f"PNG: {png_path}")


if __name__ == "__main__":
    main()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?