Files
doa_doppler_est/check_shared_timestamp_poisoning.py
T
josh a2713e80cf content(app-H): 补全联合扰动吸收与跨用户传播
在固定权重摘要模型中先消位置再解共享时间戳,保留时延与 Doppler 交互。
给出吸收及传播的条件、同构用户稀释规律与共识的条件性区分度和盲区。
补全符号索引与确定性自检,保持自由三维位置及地固运动导数。

NumPy 代数自检通过,几何 Jacobian 相对误差 1.03e-9。
SyncTeX 多遍编译通过,无断引用、新增缺字或溢出。
未运行捕获或定位 Monte Carlo,未验证实际污染池或非线性增益。
2026-10-12 13:25:17 +08:00

152 lines
7.1 KiB
Python

"""Appendix H deterministic algebra checks; no acquisition, localization MC or orbit acceptance.
Run: python check_shared_timestamp_poisoning.py. Tolerance: 1e-10 unless stated.
"""
import numpy as np
def check(actual, expected):
np.testing.assert_allclose(actual, expected, atol=1e-10, rtol=1e-10)
def predict(blocks, offsets, errors):
sizes = [len(a) for a in blocks]
A = np.zeros((sum(sizes), 3*len(blocks)))
P = np.eye(sum(sizes))
start = 0
for k, a in enumerate(blocks):
rows = slice(start, start+len(a))
assert np.linalg.matrix_rank(a) == 3
A[rows, 3*k:3*k+3] = a
P[rows, rows] -= a@np.linalg.pinv(a)
start += len(a)
B, e = np.vstack(offsets), np.concatenate(errors)
Z, b = P@B, P@e
assert np.linalg.matrix_rank(Z, tol=1e-10) == B.shape[1]
delta = np.linalg.lstsq(Z, b, rcond=None)[0]
positions = np.linalg.lstsq(A, e-B@delta, rcond=None)[0]
r = e-A@positions-B@delta
D = np.column_stack((A, B))
direct = np.linalg.lstsq(D, e, rcond=None)[0]
check(np.r_[positions, delta], direct)
check(r, P@(e-B@delta))
S, gamma = Z.T@Z, Z.T@b
check(delta, np.linalg.solve(S, gamma))
check(b@b-r@r, gamma@np.linalg.solve(S, gamma))
return positions.reshape(-1, 3), delta, r, D, P, B, e
def main():
# Synthetic local summary blocks: delay first, then Doppler and angles.
a = np.array([[1., .2, 0.], [.3, -.4, .2], [.4, 1., .1], [-.2, .3, 1.],
[1., 0., 0.], [0., 1., 0.], [0., 0., 1.], [.2, .1, .7]])
b = np.vstack((np.eye(2), np.zeros((6, 2))))
e = np.array([1., -.4, .7, -.2, 0., 0., 0., 0.])
p0, d0, _, _, _, _, _ = predict([a], [b], [e])
x, delta, r, D, P, B, error = predict([a]*4, [b]*4, [e]+[np.zeros(8)]*3)
check(delta, d0/4)
clean_x = -np.linalg.pinv(a)@b@d0/4
check(x[1:], np.tile(clean_x, (3, 1)))
check(x[0], np.linalg.pinv(a)@e+clean_x)
pa = P[:8, :8]
L = b.T@pa[:, :4]
check(L[:, 2:], -b.T@[email protected](a)[:, 2:4])
assert np.linalg.norm(L[:, 2:]) > .01, "Doppler must expose indirect offset coupling"
gain1 = np.linalg.norm(pa@e)**2-np.linalg.norm(predict([a], [b], [e])[2])**2
check(np.linalg.norm(P@error)**2-r@r, gain1/4)
S = B.T@P@B
metric = L.T@np.linalg.solve(S, L)
cross = 2*e[:2]@metric[:2, 2:]@e[2:4]
check(e[:2]@metric[:2, :2]@e[:2]+cross+e[2:4]@metric[2:, 2:]@e[2:4], gain1/4)
assert abs(cross) > .01, "Joint error must expose a nonzero cross term"
common = np.array([.4, -.3])
x_common, d_common, r_common, *_ = predict([a]*4, [b]*4, [b@common]*4)
check(x_common, np.zeros((4, 3)))
check(d_common, common)
check(r_common, np.zeros(32))
check(predict([a], [b], [b@common])[0], np.zeros((1, 3)))
gamma = b.T@pa@e
assert np.linalg.norm(gamma) > .01
cancelling = np.linalg.svd(L)[2][-1]
x_cancel, d_cancel, _, *_ = predict([a]*4, [b]*4,
[np.r_[cancelling, np.zeros(4)]]+[np.zeros(8)]*3)
check(d_cancel, np.zeros(2))
check(x_cancel[1:], np.zeros((3, 3)))
orthogonal = np.vstack((np.zeros((2, 3)), np.eye(3)))
orthogonal_b = np.vstack((np.eye(2), np.zeros((3, 2))))
orthogonal_e = orthogonal_b@common
check(predict([orthogonal]*2, [orthogonal_b]*2,
[orthogonal_e, np.zeros(5)])[0], np.zeros((2, 3)))
H = D@np.linalg.pinv(D)
selection = np.eye(len(error))[:, :4]
check(r@r, e[:4]@(np.eye(4)-selection.T@H@selection)@e[:4])
assert np.linalg.matrix_rank(D[4:]) == D.shape[1]
spike = np.eye(len(error))[:, 0]
h = H[0, 0]
spike_r = (np.eye(len(error))-H)@spike
check(spike_r[0], 1-h)
check(spike_r@spike_r, 1-h)
check(spike_r[1:]@spike_r[1:], h*(1-h))
assert np.linalg.norm(pa@e) > np.linalg.norm(r[:8])
left_out_delta = predict([a]*3, [b]*3, [np.zeros(8)]*3)[1]
check(left_out_delta, np.zeros(2))
check(pa@(e-b@left_out_delta), pa@e)
# Exact absorption counterexample: other users cannot anchor two offset directions.
attacked = np.array([[1., 0., .4], [0., 1., -.3], [.2, .4, 1.],
[1., 0., 0.], [0., 1., 0.]])
clean = np.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.],
[0., 0., 1.], [0., 0., 1.]])
bc = np.vstack((np.eye(2), np.zeros((3, 2))))
corrupted = attacked@np.array([0., 0., .7])+bc@common
assert np.linalg.norm(corrupted[2]) > 0 and np.linalg.norm(corrupted[3:]) == 0
xc, dc, rc, Dc, *_ = predict([attacked]+[clean]*3, [bc]*4,
[corrupted]+[np.zeros(5)]*3)
check(dc, common)
check(xc[1:], np.tile(np.r_[-common, 0.], (3, 1)))
check(rc, np.zeros(20))
assert np.linalg.matrix_rank(Dc[3:]) < Dc.shape[1]
check((np.eye(5)-clean@np.linalg.pinv(clean))@bc, np.zeros((5, 2)))
# A common offset changes neither user contrasts nor median-centered residuals.
samples = np.array([.2, -.3, .4, .1])
check(samples-np.median(samples), (samples+4)-np.median(samples+4))
small = np.array([-3.4e-5, -3.3e-5, -3.3e-5, 1e-4])
center, width = -3.35e-5, .5e-6
difference = small[-1]-center
contrast = abs(small[-1]-np.median(small))/(1.4826*np.median(abs(small-np.median(small)))+1e-9)
lower = (abs(difference)-width)/(2*1.4826*width+1e-9)
assert max(abs(small)) < .001 and contrast >= lower > 50
# Free 3-D geometry includes position-dependent Earth-fixed user velocity.
spin = np.array([[0., -7.292115e-5, 0.], [7.292115e-5, 0., 0.], [0., 0., 0.]])
satellites = np.array([[6.9e6, 1e5, 2e5], [6.8e6, -3e5, 4e5], [7e6, 4e5, -2e5]])
velocities = np.array([[0., 7200., 400.], [1200., 6800., -500.], [-1500., 7000., 100.]])
position = np.array([6.37e6, 3e4, 7e4])
def summaries(p):
ranges = np.linalg.norm(p-satellites, axis=1)
directions = (p-satellites)/ranges[:, None]
doppler = np.sum(directions*(velocities-spin@p), axis=1)
return np.r_[(ranges[1:]-ranges[0])/299792458e-6,
2*np.pi*1e-6/.2*(doppler[1:]-doppler[0])]
ranges = np.linalg.norm(position-satellites, axis=1)
directions = (position-satellites)/ranges[:, None]
tangents = [(np.eye(3)-np.outer(u, u))/r for u, r in zip(directions, ranges)]
doppler_jac = np.array([(v-spin@position)@t-u@spin
for v, t, u in zip(velocities, tangents, directions)])
analytic = np.vstack(((directions[1:]-directions[0])/299792458e-6,
2*np.pi*1e-6/.2*(doppler_jac[1:]-doppler_jac[0])))
step = .1
numeric = np.column_stack([(summaries(position+step*basis)-summaries(position-step*basis))/(2*step)
for basis in np.eye(3)])
geometry_error = np.linalg.norm(analytic-numeric)/np.linalg.norm(analytic)
assert geometry_error < 1e-7, geometry_error
print(f"Free-3D delay/Doppler Jacobian relative error: {geometry_error:.3g}")
print("PASS: block maps, joint interactions, 1/K law, zero-coupling/cancellation,")
print(" single-row recovery, deletion rank, exact absorption and consensus invariance.")
print("Synthetic linear blocks only; real masks/scales and nonlinear prediction remain code-side checks.")
if __name__ == "__main__":
main()