在固定权重摘要模型中先消位置再解共享时间戳,保留时延与 Doppler 交互。 给出吸收及传播的条件、同构用户稀释规律与共识的条件性区分度和盲区。 补全符号索引与确定性自检,保持自由三维位置及地固运动导数。 NumPy 代数自检通过,几何 Jacobian 相对误差 1.03e-9。 SyncTeX 多遍编译通过,无断引用、新增缺字或溢出。 未运行捕获或定位 Monte Carlo,未验证实际污染池或非线性增益。
152 lines
7.1 KiB
Python
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()
|