TEORİDEN UYGULAMAYA · PYTHON EŞLİKÇİSİ

Hiperbol

Prof. Dr. Zühal Küçükarslan Yüzbaşı · Prof. Dr. Bahadır Yüzbaşı

Bölüm 14 · v1.0 · Türkçe

Bu bölümün laboratuvarı, kitaptaki örnekleri yeniden üretmek ve sayısal sonuçları incelemek için hazırlanmıştır. Matematiksel tanımlar, ispatlar ve sorular kitapta yer alır.

Laboratuvarı indir (.py) Tam Python paketi (.zip)

Laboratuvar kodu

"""Bölüm 14: hiperbolik TDOA konum belirleme laboratuvarı.

Model yalnız iki boyutlu, sabit yayılma hızlı, eşzamanlı saatli ve gürültüsüz
bir sistemi temsil eder. Çok yollu yayılım, engeller, kırılma, saat
sürüklenmesi, hız değişimi, sensör belirsizliği ve üç boyutlu konum modele
dahil değildir.
"""

from __future__ import annotations

import numpy as np
from scipy.optimize import least_squares

from agbook import (
    canonical_array_sha256,
    hyperbola_diagnostics,
    hyperbola_frame_from_foci,
    rotation_matrix_2d,
)


def signed_distance_difference(
    point_m: np.ndarray,
    first_receiver_m: np.ndarray,
    second_receiver_m: np.ndarray,
) -> float:
    """İlk ve ikinci alıcıya uzaklıkların işaretli farkını metreyle verir."""

    return float(
        np.linalg.norm(point_m - first_receiver_m)
        - np.linalg.norm(point_m - second_receiver_m)
    )


def tdoa_residuals_m(
    point_m: np.ndarray,
    receivers_m: np.ndarray,
    measured_differences_m: np.ndarray,
) -> np.ndarray:
    """AB ve AC çiftlerinin model-ölçüm uzaklık farkı artıklarını verir."""

    receiver_a_m, receiver_b_m, receiver_c_m = receivers_m
    modeled_m = np.array(
        [
            signed_distance_difference(point_m, receiver_a_m, receiver_b_m),
            signed_distance_difference(point_m, receiver_a_m, receiver_c_m),
        ]
    )
    return modeled_m - measured_differences_m


def tdoa_jacobian(
    point_m: np.ndarray,
    receivers_m: np.ndarray,
    measured_differences_m: np.ndarray | None = None,
) -> np.ndarray:
    """İki uzaklık-fark denkleminin noktaya göre boyutsuz Jacobianını verir."""

    del measured_differences_m
    offsets = point_m - receivers_m
    distances_m = np.linalg.norm(offsets, axis=1)
    if np.any(distances_m == 0.0):
        raise ValueError("Kaynak konumu bir alıcıyla çakışamaz.")
    unit_directions = offsets / distances_m[:, None]
    return np.vstack(
        [
            unit_directions[0] - unit_directions[1],
            unit_directions[0] - unit_directions[2],
        ]
    )


def main() -> None:
    wave_speed_m_s = 343.0
    receivers_m = np.array(
        [
            [-6.0, 0.0],
            [6.0, 0.0],
            [0.0, 8.0],
        ]
    )
    true_source_m = np.array([2.0, 3.0])

    measured_differences_m = np.array(
        [
            signed_distance_difference(true_source_m, receivers_m[0], receivers_m[1]),
            signed_distance_difference(true_source_m, receivers_m[0], receivers_m[2]),
        ]
    )
    measured_tdoa_s = measured_differences_m / wave_speed_m_s

    pair_indices = ((0, 1), (0, 2))
    frames = []
    branches = []
    sample_diagnostics = []
    sample_parameters = np.linspace(-1.5, 1.5, 13)
    for difference_m, (first_index, second_index) in zip(
        measured_differences_m,
        pair_indices,
        strict=True,
    ):
        semi_transverse_m = 0.5 * abs(float(difference_m))
        frame = hyperbola_frame_from_foci(
            receivers_m[first_index],
            receivers_m[second_index],
            semi_transverse_m,
        )
        branch = 1 if difference_m > 0.0 else -1
        frames.append(frame)
        branches.append(branch)
        sample_diagnostics.append(
            hyperbola_diagnostics(
                frame.center,
                frame.transverse_direction,
                frame.semi_transverse,
                frame.semi_conjugate,
                sample_parameters,
                branch=branch,
            )
        )

    initial_guess_m = np.array([1.0, 2.0])
    result = least_squares(
        tdoa_residuals_m,
        initial_guess_m,
        jac=tdoa_jacobian,
        args=(receivers_m, measured_differences_m),
        method="trf",
        xtol=1e-14,
        ftol=1e-14,
        gtol=1e-14,
        max_nfev=100,
    )
    estimated_source_m = result.x
    final_residuals_m = tdoa_residuals_m(
        estimated_source_m,
        receivers_m,
        measured_differences_m,
    )
    position_error_m = float(np.linalg.norm(estimated_source_m - true_source_m))
    jacobian = tdoa_jacobian(estimated_source_m, receivers_m)
    singular_values = np.linalg.svd(jacobian, compute_uv=False)
    condition_number = float(singular_values[0] / singular_values[-1])

    assert result.success
    np.testing.assert_allclose(estimated_source_m, true_source_m, atol=2e-13)
    np.testing.assert_allclose(final_residuals_m, 0.0, atol=2e-13)
    assert np.linalg.matrix_rank(jacobian) == 2
    for difference_m, branch, diagnostic in zip(
        measured_differences_m,
        branches,
        sample_diagnostics,
        strict=True,
    ):
        np.testing.assert_allclose(
            diagnostic.signed_focal_differences,
            difference_m,
            atol=3e-14,
        )
        assert diagnostic.branch == branch
        assert diagnostic.maximum_absolute_algebraic_residual < 2e-14
        assert diagnostic.maximum_absolute_focal_difference_residual < 4e-14

    rotation = rotation_matrix_2d(np.deg2rad(29.0))
    translation_m = np.array([11.0, -4.0])
    moved_receivers_m = receivers_m @ rotation.T + translation_m
    moved_source_m = true_source_m @ rotation.T + translation_m
    moved_differences_m = np.array(
        [
            signed_distance_difference(
                moved_source_m,
                moved_receivers_m[first_index],
                moved_receivers_m[second_index],
            )
            for first_index, second_index in pair_indices
        ]
    )
    np.testing.assert_allclose(moved_differences_m, measured_differences_m, atol=3e-15)

    signature_values = np.concatenate(
        [
            np.array([wave_speed_m_s]),
            receivers_m.ravel(),
            true_source_m,
            measured_differences_m,
            measured_tdoa_s,
            initial_guess_m,
            estimated_source_m,
            final_residuals_m,
            jacobian.ravel(),
            singular_values,
            np.array(branches, dtype=float),
            sample_parameters,
            sample_diagnostics[0].points.ravel(),
            sample_diagnostics[1].points.ravel(),
            rotation.ravel(),
            translation_m,
            moved_receivers_m.ravel(),
            moved_source_m,
        ]
    )

    print("uzunluk_birimi: m")
    print("zaman_birimi: s")
    print("model: sentetik_iki_boyutlu_gurultusuz_tdoa")
    print("gercek_konumlandirma_sistemi_mi: False")
    print("yayilma_hizi_m_s:", wave_speed_m_s)
    print("alicilar_m:", receivers_m.tolist())
    print("gercek_kaynak_m:", true_source_m)
    print("olculen_uzaklik_farklari_m:", measured_differences_m)
    print("olculen_tdoa_s:", measured_tdoa_s)
    print("hiperbol_kollari:", branches)
    print("tahmin_edilen_kaynak_m:", estimated_source_m)
    print("konum_hatasi_m:", f"{position_error_m:.3e}")
    print("son_uzaklik_fark_artiklari_m:", final_residuals_m)
    print("jacobian_ranki:", int(np.linalg.matrix_rank(jacobian)))
    print("jacobian_kosul_sayisi:", f"{condition_number:.6f}")
    print(
        "en_buyuk_ornekleme_cebirsel_artigi:",
        f"{max(d.maximum_absolute_algebraic_residual for d in sample_diagnostics):.3e}",
    )
    print(
        "en_buyuk_ornekleme_odak_fark_artigi_m:",
        f"{max(d.maximum_absolute_focal_difference_residual for d in sample_diagnostics):.3e}",
    )
    print("rijit_harekette_olculer_korundu_mu:", bool(np.allclose(moved_differences_m, measured_differences_m)))
    print("bilimsel_imza:", canonical_array_sha256(signature_values))


if __name__ == "__main__":
    main()
Doğrulama çalıştırmasının çıktısı
uzunluk_birimi: m
zaman_birimi: s
model: sentetik_iki_boyutlu_gurultusuz_tdoa
gercek_konumlandirma_sistemi_mi: False
yayilma_hizi_m_s: 343.0
alicilar_m: [[-6.0, 0.0], [6.0, 0.0], [0.0, 8.0]]
gercek_kaynak_m: [2. 3.]
olculen_uzaklik_farklari_m: [3.54400375 3.15883894]
olculen_tdoa_s: [0.01033237 0.00920944]
hiperbol_kollari: [1, 1]
tahmin_edilen_kaynak_m: [2. 3.]
konum_hatasi_m: 6.661e-16
son_uzaklik_fark_artiklari_m: [-8.8817842e-16  0.0000000e+00]
jacobian_ranki: 2
jacobian_kosul_sayisi: 1.432511
en_buyuk_ornekleme_cebirsel_artigi: 3.109e-15
en_buyuk_ornekleme_odak_fark_artigi_m: 1.776e-15
rijit_harekette_olculer_korundu_mu: True
bilimsel_imza: cbdde3b28e561fadc87871fee59cc734e3df6a7bc150c46c73ffc0875824488b
Metindeki Python kodları

Kod dosyasını indir

# Kitaptaki kod blokları; bu dosya içinde sırayla çalıştırılır.
# --- 1. Hiperbol üyelik artıkları ---
import numpy as np
from agbook import (
    hyperbola_algebraic_residuals,
    hyperbola_focal_difference_residuals,
    hyperbola_frame_from_center_axes,
    hyperbola_points,
)

frame = hyperbola_frame_from_center_axes([0., 0.], [1., 0.], 3., 4.)
t = np.linspace(-2., 2., 17)
points = np.vstack([
    hyperbola_points([0., 0.], [1., 0.], 3., 4., t, branch=-1),
    hyperbola_points([0., 0.], [1., 0.], 3., 4., t, branch=1),
])
r_alg = hyperbola_algebraic_residuals(
    points, frame.center, frame.transverse_direction, 3., 4.
)
r_diff = hyperbola_focal_difference_residuals(
    points, frame.first_focus, frame.second_focus, 3.
)
print(np.max(np.abs(r_alg)), np.max(np.abs(r_diff)))
assert np.max(np.abs(r_alg)) < 2e-14
assert np.max(np.abs(r_diff)) < 4e-14

Kaydedilen çıktı

1. Hiperbol üyelik artıkları
1.7763568394002505e-15 1.7763568394002505e-15
Çözümlerdeki Python kodları

Kod dosyasını indir

# Kitaptaki kod blokları; bu dosya içinde sırayla çalıştırılır.
# --- 1. İki hiperbol kurucusunun eşdeğerliği ---
import numpy as np
from agbook import (
    hyperbola_frame_from_center_axes,
    hyperbola_frame_from_foci,
)

first = hyperbola_frame_from_center_axes([1., -2.], [2., 1.], 4., 3.)
second = hyperbola_frame_from_foci(
    first.first_focus, first.second_focus, first.semi_transverse
)

for name in (
    "center", "first_focus", "second_focus", "transverse_direction",
    "first_directrix", "second_directrix", "first_asymptote",
    "second_asymptote",
):
    assert np.allclose(getattr(first, name), getattr(second, name))
assert np.isclose(first.semi_conjugate, second.semi_conjugate)
assert np.isclose(first.eccentricity, second.eccentricity)
print(first.focal_distance, first.eccentricity)

# --- 2. İki kolda üyelik ve teğet artıkları ---
import numpy as np
from agbook import (
    hyperbola_algebraic_residuals,
    hyperbola_focal_difference_residuals,
    hyperbola_frame_from_center_axes,
    hyperbola_points,
    hyperbola_tangent_line,
)

frame = hyperbola_frame_from_center_axes([0., 0.], [1., 0.], 3., 4.)
t = np.linspace(-1.5, 1.5, 9)
for branch in (-1, 1):
    points = hyperbola_points([0., 0.], [1., 0.], 3., 4., t, branch=branch)
    r_alg = hyperbola_algebraic_residuals(
        points, [0., 0.], [1., 0.], 3., 4.
    )
    r_diff = hyperbola_focal_difference_residuals(
        points, frame.first_focus, frame.second_focus, 3.
    )
    lines = np.vstack([
        hyperbola_tangent_line(
            [0., 0.], [1., 0.], 3., 4., value, branch=branch
        ) for value in t
    ])
    r_tan = np.sum(lines[:, :2] * points, axis=1) + lines[:, 2]
    print(branch, np.max(abs(r_alg)), np.max(abs(r_diff)), np.max(abs(r_tan)))
    assert np.max(abs(r_alg)) < 2e-14
    assert np.max(abs(r_diff)) < 4e-14
    assert np.max(abs(r_tan)) < 2e-14

# --- 3. Asimptot yakınsaması ve rijit hareket ---
import numpy as np
from agbook import (
    hyperbola_diagnostics,
    hyperbola_points,
    rotation_matrix_2d,
)

t = np.array([0., 1., 2., 3.])
before = hyperbola_diagnostics([0., 0.], [1., 0.], 3., 2., t, branch=1)
print(before.nearest_asymptote_distances)
assert np.all(np.diff(before.nearest_asymptote_distances) < 0.)

Q = rotation_matrix_2d(np.deg2rad(37.))
shift = np.array([5., -3.])
after = hyperbola_diagnostics(shift, Q @ np.array([1., 0.]), 3., 2., t, branch=1)
expected = before.points @ Q.T + shift
assert np.allclose(after.points, expected)
assert np.allclose(
    after.signed_focal_differences, before.signed_focal_differences
)

# --- 4. Ölçek değişmezliği ve açık sınır hataları ---
import numpy as np
from agbook import (
    hyperbola_algebraic_residuals,
    hyperbola_focal_difference_residuals,
    hyperbola_frame_from_center_axes,
    hyperbola_points,
)

m = hyperbola_frame_from_center_axes([0., 0.], [1., 0.], 3., 4.)
mm = hyperbola_frame_from_center_axes([0., 0.], [1., 0.], 3000., 4000.)
assert np.isclose(mm.focal_distance, 1000. * m.focal_distance)
assert np.isclose(mm.eccentricity, m.eccentricity)

p = hyperbola_points([0., 0.], [1., 0.], 3., 4., 0.8, branch=1)
p_bad = p + np.array([0.01, -0.02])
r_m = hyperbola_focal_difference_residuals(
    p_bad, m.first_focus, m.second_focus, 3.
)
r_mm = hyperbola_focal_difference_residuals(
    1000. * p_bad, mm.first_focus, mm.second_focus, 3000.
)
assert np.isclose(r_mm, 1000. * r_m)
assert np.isclose(
    hyperbola_algebraic_residuals(p_bad, [0., 0.], [1., 0.], 3., 4.),
    hyperbola_algebraic_residuals(
        1000. * p_bad, [0., 0.], [1., 0.], 3000., 4000.
    ),
)

bad_calls = [
    lambda: hyperbola_frame_from_center_axes([0., 0.], [1., 0.], 0., 4.),
    lambda: hyperbola_points([0., 0.], [1., 0.], 3., 4., 0., branch=0),
    lambda: hyperbola_points([0., 0.], [1., 0.], 3., 4., [], branch=1),
]
for call in bad_calls:
    try:
        call()
    except ValueError as error:
        print(type(error).__name__, str(error))

Kaydedilen çıktı

1. İki hiperbol kurucusunun eşdeğerliği
5.0 1.25

2. İki kolda üyelik ve teğet artıkları
-1 8.881784197001252e-16 1.7763568394002505e-15 8.881784197001252e-16
1 8.881784197001252e-16 1.7763568394002505e-15 8.881784197001252e-16

3. Asimptot yakınsaması ve rijit hareket
[1.66410059 0.61218839 0.22521152 0.08285069]

4. Ölçek değişmezliği ve açık sınır hataları
ValueError semi_transverse kesin pozitif olmalıdır.
ValueError branch yalnız -1 veya +1 olmalıdır.
ValueError parameters, bir skaler veya boş olmayan (n,) dizisi olmalıdır.

Çalıştırma rehberi

Önce tam Python paketini indirin ve arşivi açın. Aşağıdaki komutları arşivin üst klasöründen başlatın. İlk kurulum internet bağlantısı ve Python 3.12 veya 3.13 gerektirir. Bağımlılıklar sabittir; sistem Python'unu değiştirmemek için ayrı sanal ortam kullanılır.

macOS / Linux
cd analitik-geometri-python-v1.0
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
python labs/bolum_14_hiperbol_tdoa.py
python -m pytest
Windows PowerShell
cd analitik-geometri-python-v1.0
py -3.13 -m venv .venv
.venv\Scripts\python.exe -m pip install -e ".[dev]"
.venv\Scripts\python.exe labs/bolum_14_hiperbol_tdoa.py
.venv\Scripts\python.exe -m pytest

Laboratuvar dosyaları agbook yardımcı paketini kullanır; tek bir dosyayı indirmek paketi kurmanın yerini tutmaz. Metin/çözüm kodları kendi dosyaları içinde sırayla çalıştırılır; gizli bir notebook oturumu gerekmez. Son ondalık basamaklar platforma göre değişebilir.