"""Frozen synthetic GNSS replay with independent ionosphere/common-error demos. Original solver is imported unchanged. 'good' is its historical name, not an accuracy claim. Coordinates are teaching Cartesian XYZ, not local ENU. """ import csv import math import time from pathlib import Path from gnss_kernel import SATELLITES, solve DEFAULT = dict(geometry='good',range_step_m=.08,tec=12,common_error_m=1.25,local_error_m=.08) BOUNDS = dict(range_step_m=(0,.16),tec=(0,40),common_error_m=(-5,5),local_error_m=(-1,1)) TRUTH = (120.,-80.,45.) CLOCK = 1.25 def observations(p): if set(p) != set(DEFAULT) or p['geometry'] not in SATELLITES: raise ValueError('Unknown parameters or geometry') for key, (lo,hi) in BOUNDS.items(): v=p[key] if type(v) not in (int,float) or not math.isfinite(v) or not lo<=v<=hi: raise ValueError('Parameter out of range: '+key) step=1 if key=='tec' else .01 if abs((v-lo)/step-round((v-lo)/step))>1e-8: raise ValueError('Parameter off step: '+key) with Path(__file__).with_name('synthetic.csv').open(newline='') as f: rows=[r for r in csv.DictReader(f) if r['geometry']==p['geometry']] for r in rows: r['pseudorange_m']=float(r['pseudorange_m'])+(p['range_step_m']-.08)*int(r['satellite_id']) return rows def run(name,p): if name!='gnss': raise ValueError('Unknown experiment') start=time.perf_counter() rows=observations(p) result=solve(rows,p['geometry']) position=[result[k] for k in ('x_m','y_m','z_m')] satellites=SATELLITES[p['geometry']] errors=[v-t for v,t in zip(position,TRUTH)]+[result['clock_bias_m']-CLOCK] residual=[r['pseudorange_m']-(math.dist(s,position)+result['clock_bias_m']) for s,r in zip(satellites,rows)] # These scalar demonstrations are intentionally independent of the replay. f1,f2=1575.42e6,1227.60e6 i1=40.3*p['tec']*1e16/f1**2 i2=40.3*p['tec']*1e16/f2**2 combined=(f1*f1*i1-f2*f2*i2)/(f1*f1-f2*f2) common,local=p['common_error_m'],p['local_error_m'] def metric(ident,value,unit='m'): return dict(id=ident,value=value,unit=unit) def series(ident,x,y,x_unit='index',y_unit='m'): return dict(id=ident,x=list(x),y=list(y),x_unit=x_unit,y_unit=y_unit) # Unit ray components at the known synthetic receiver; fixed [-1,1] plots. rays=[[(s[j]-TRUTH[j])/math.dist(s,TRUTH) for j in range(3)] for s in satellites] return dict( metrics=[metric('position_error_m',math.dist(position,TRUTH)), metric('z_error_m',errors[2]),metric('clock_error_m',errors[3]), metric('residual_rms_m',result['residual_rms_m']), metric('xy_dop',result['hdop'],'1'),metric('xyz_dop',result['pdop'],'1'), metric('iono_l1_m',i1),metric('iono_l2_m',i2),metric('iono_free_m',combined), metric('common_error_removed_m',common),metric('single_error_m',common+local), metric('differenced_error_m',local)], series=[series('satellite_xy',[r[0] for r in rays],[r[1] for r in rays],'1','1'), series('satellite_xz',[r[0] for r in rays],[r[2] for r in rays],'1','1'), series('state_errors',range(4),errors), series('range_errors',range(4),[r['pseudorange_m']-math.dist(s,TRUTH)-CLOCK for s,r in zip(satellites,rows)]), series('postfit_residuals',range(4),residual), series('ionosphere_terms',range(3),[i1,i2,combined]), series('common_terms',range(3),[common+local,common,local])], stdout='',provenance=dict(data_type='synthetic',compute_ms=(time.perf_counter()-start)*1000, solver='Unchanged original normal-equation solver, maximum 12 iterations', replay_result=result,truth_xyz_m=list(TRUTH),truth_clock_bias_m=CLOCK, limitations='Both geometries ill-conditioned; zero residual is not accuracy. XY DOP is not geodetic HDOP. Ionosphere/common-error demos do not modify replay. No RTK ambiguity fixing.')) if __name__=='__main__': import argparse,json parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--parameters',default=json.dumps(DEFAULT)) print(json.dumps(run('gnss',json.loads(parser.parse_args().parameters)),allow_nan=False))