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 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ı
# 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ı
# 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 pytestWindows 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