"""Bounded, reproducible profile timing of an explicit trapezoid template. Formal intervals condition on fixed duration/ingress, independent Gaussian source errors and this baseline. They are not full physical-transit errors. """ from pathlib import Path from html import escape import csv, hashlib, json, math, shutil, statistics MODEL='integrated-trapezoid-linear-baseline-v1' MAX_ROWS=2000 def _keys(v, required): if not isinstance(v,dict) or set(v)!=set(required):raise ValueError('shape') def _n(v,lo,hi): if isinstance(v,bool) or not isinstance(v,(int,float)) or not math.isfinite(v) or not lo<=v<=hi:raise ValueError('number') return float(v) def validate_dataset(d): import re _keys(d,['schemaVersion','targetId','timeSystem','rows']) if d['schemaVersion']!=1 or isinstance(d['schemaVersion'],bool) or not isinstance(d['targetId'],str) or not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._ -]{0,99}',d['targetId']) or d['timeSystem']!='BJD_TDB' or not isinstance(d['rows'],list) or not 15<=len(d['rows'])<=MAX_ROWS:raise ValueError('dataset') rows=[] for x in d['rows']: _keys(x,['time','flux','error','exposureSeconds']) row={'time':_n(x['time'],2e6,3e6),'flux':_n(x['flux'],0,10),'error':_n(x['error'],1e-9,1),'exposureSeconds':_n(x['exposureSeconds'],0,7200)} if rows and row['time']<=rows[-1]['time']:raise ValueError('time-order') rows.append(row) if rows[-1]['time']-rows[0]['time']>30:raise ValueError('time-span') return {'schemaVersion':1,'targetId':d['targetId'],'timeSystem':'BJD_TDB','rows':rows} def validate_method(m,d): _keys(m,['kind','centerMin','centerMax','durationDays','ingressDays']) if m['kind']!=MODEL:raise ValueError('method') a=_n(m['centerMin'],d['rows'][0]['time'],d['rows'][-1]['time']);b=_n(m['centerMax'],a,d['rows'][-1]['time']);D=_n(m['durationDays'],.005,1);I=_n(m['ingressDays'],.0001,D/2) if not D/5<=b-a<=2*D:raise ValueError('center-range') return {'kind':MODEL,'centerMin':a,'centerMax':b,'durationDays':D,'ingressDays':I} def primitive(x,D,I): h=D/2 if x<=-h:return 0. if x<-h+I:return (x+h)**2/(2*I) if x<=h-I:return I/2+x+h-I if x0:return max(0.,min(1.,(primitive(x+e/2,D,I)-primitive(x-e/2,D,I))/e)) h=D/2 return 0. if abs(x)>=h else min(1.,(h-abs(x))/I) def inverse3(A): # Partial-pivot Gauss-Jordan; absolute scale relative to largest diagonal. scale=max(abs(A[i][i]) for i in range(3));M=[list(A[i])+[float(i==j) for j in range(3)]for i in range(3)] if not scale>0:raise ValueError('singular') for k in range(3): p=max(range(k,3),key=lambda i:abs(M[i][k])) if abs(M[p][k])4:raise ValueError('profile-complex') def refine(i): a=lo+(i-1)*step;b=lo+(i+1)*step;gold=(math.sqrt(5)-1)/2;c=b-gold*(b-a);e=a+gold*(b-a);fc=evaluate(c)['chi2'];fe=evaluate(e)['chi2'] for _ in range(26): if fc0 or min(v['beta'][0]-v['beta'][1]/2,v['beta'][0]+v['beta'][1]/2)<=0 or not 00 or depth/ds<5:raise ValueError('weak-or-nontransit') if sum(shape(r['time']-center,D,I,r['exposureSeconds'])>.1 for r in rows)<4 or sum(r['time']center+D/2+pad for r in rows)<5:raise ValueError('incomplete-event') if min(center-lo,hi-center)<(hi-lo)*1e-6:raise ValueError('edge') threshold=v['chi2']+1 def crossing(bound): previous=center;bracket=None for step in range(1,65): point=center+(bound-center)*step/64 q=evaluate(point)['chi2'] if q>threshold: if bracket is None:bracket=(previous,point) elif bracket is not None:raise ValueError('profile-ambiguous') previous=point if bracket is None:raise ValueError('unbounded-interval') near,far=bracket for _ in range(22): mid=(near+far)/2 if evaluate(mid)['chi2']>threshold:far=mid else:near=mid return (near+far)/2 left=crossing(lo);right=crossing(hi);dof=len(rows)-4 result={'centerBjdTdb':center,'sigmaMinusSeconds':(center-left)*86400,'sigmaPlusSeconds':(right-center)*86400,'depth':depth,'depthSigma':ds,'fractionalDepth':depth/base,'baselineAtCenter':base,'slopePerDay':v['beta'][1]/span,'chi2':v['chi2'],'dof':dof,'reducedChi2':v['chi2']/dof,'rows':len(rows),'model':MODEL,'conditionalUncertainty':True,'searchGridPoints':61,'refinementIterations':26,'profileScanSteps':64} if not all(math.isfinite(v)for v in result.values()if isinstance(v,(int,float))):raise ValueError('nonfinite-result') return result def prepare_example(root): root=Path(root);source=root/'content/notebooks-8/kepler8-q0-lightcurve.csv';raw=source.read_bytes();manifest=json.loads((root/'content/notebooks-8/datasets.json').read_text())['transit'];assert hashlib.sha256(raw).hexdigest()==manifest['sha256'] rows=[] for r in csv.DictReader(raw.decode().splitlines()): t=float(r['time_bkjd']) if 120.6<=t<=121.65:rows.append({'time':t+2454833,'flux':float(r['relative_flux']),'error':float(r['relative_flux_error']),'exposureSeconds':1765.461685942}) d={'schemaVersion':1,'targetId':'Kepler-8','timeSystem':'BJD_TDB','rows':rows};m={'kind':MODEL,'centerMin':2454954.04,'centerMax':2454954.22,'durationDays':.14,'ingressDays':.025} provenance={'sourceType':'archived-example','sourceUrl':manifest['sourceUrl'],'sourceSha256':manifest['sha256'],'rawSha256':manifest['rawSha256'],'timeConversion':'BKJD + 2454833','calibrationStatus':'not-verified','notes':'Actual retained quality=0 PDCSAP light curve, BKJD120.6–121.65 only. Relative flux/error retain source CSV values. The 1765.461685942s uniform model averaging window approximates Kepler TIMEDEL; it is not a claim of uninterrupted photon exposure. Fixed duration 0.14 d / ingress 0.025 d are template choices, not physical parameter estimates. No new observation or independent review.'} out={'schema':'org.spacetrc.observation-input','schemaVersion':1,'dataset':d,'method':m,'provenance':provenance};folder=root/'content/measurement-10';folder.mkdir(parents=True,exist_ok=True);(folder/'kepler8-input.json').write_text(json.dumps(out,ensure_ascii=False,indent=2)+'\n');(folder/'kepler8-reference.json').write_text(json.dumps(fit(d,m),indent=2)+'\n');return out def component(root,en=False): t=lambda a,b:b if en else a;example=prepare_example(root);p=Path(root)/'content/measurement-10/kepler8-input.json';cfg={'schemaVersion':1,'examplePath':'measurement-10/kepler8-input.json','exampleSha256':hashlib.sha256(p.read_bytes()).hexdigest(),'maxBytes':2097152,'maxRows':2000} h='

SPACETRC / OBSERVATION ANALYSIS

'+t('Ölçümü aç.
Zamanı belirsizliğiyle hesapla.','Open the measurement.
Calculate timing with uncertainty.')+'

'+t('Gerçek bir Kepler-8 arşiv geçişini veya kendi sınırlandırılmış ışık eğrini, açık bir şablonla analiz et. Kaynak, zaman sistemi ve koşullu hesap birlikte taşınır.','Analyse an actual archived Kepler-8 transit or your own bounded light curve with an explicit template. Source, time system and conditional calculation travel together.')+'

'+t('1 · Veriyi açık seçimle yükle','1 · Explicitly load data')+'

'+t('CSV başlığı: time,flux,error veya time_bkjd,relative_flux,relative_flux_error,quality_flag. Zamanlar artan sırada, hatalar pozitif olmalı. Gösterilmeyen satırlar sessizce doldurulmaz; 15–2000 satır ve en çok 30 günlük aralık gerekir. Yerel dosya sunucuya gönderilmez.','CSV header: time,flux,error or time_bkjd,relative_flux,relative_flux_error,quality_flag. Times must increase and errors must be positive. Missing rows are not silently invented; 15–2000 rows and at most 30 days are allowed. The local file is not uploaded.')+'

'+t('Henüz veri açılmadı. Sayfa açılışında veri indirilmez veya hesap başlatılmaz.','No dataset opened. Page load neither downloads data nor starts calculation.')+'

'+t('2 · Yöntemi ve aralığı belirt','2 · Specify the method and interval')+'

' for k,tr,eng in [('centerMin','En erken merkez · BJD_TDB','Earliest centre · BJD_TDB'),('centerMax','En geç merkez · BJD_TDB','Latest centre · BJD_TDB'),('durationDays','Toplam şablon süresi · gün','Total template duration · days'),('ingressDays','Her giriş/çıkış süresi · gün','Each ingress/egress duration · days')]:h+='' h+='

'+t('Sabit süre ve giriş/çıkış biçimine sahip, poz penceresiyle ortalanmış trapez; doğrusal temel çizgisi ve ağırlıklı hata hesabı. Sonuç seçilen model ve bağımsız Gauss hataları koşulundadır. Tam fiziksel geçiş modeli, kırmızı gürültü veya kalibrasyon doğrulaması değildir.','A fixed-duration/ingress trapezoid averaged across the exposure window, with a linear baseline and weighted errors. The result conditions on this model and independent Gaussian errors. It is not a full physical transit model, correlated-noise analysis or calibration verification.')+'

'+t('3 · Sonucu ve kapsamı incele','3 · Inspect result and scope')+'

'+t('Kaynak ve dönüşüm kaydı','Source and transformation record')+'

'+t('Dışa aktarılan sonuç, başka bir kişinin kör tekrarının veya bağımsız incelemesinin yerine geçmez. Kendi sonuç dosyanızı diğer katılımcıyla paylaşmak körlüğü bozar.','An exported result does not replace another person’s blind repetition or independent review. Sharing your result file with another participant breaks blinding.')+'

'+t('Gerçek hesaplarla ayrı tekrar','Separate replication with actual accounts')+' ↗

'+t('Bilimsel sınırlar','Scientific limits')+'

'+t('61 noktalı merkez taraması en çok dört yerel minimumu inceltir; çoklu aday veya daha karmaşık profil hesaplanmış bir sonuç olarak sunulmaz. Biçimsel merkez aralığı, her yönde 64 adımlık taramada bulunan ilk Δχ²=1 kesişiminden gelir; tarama çoklu bir güven bölgesi görürse hesap reddedilir. Sonlu tarama çözünürlüğü, tüm olası en küçük bölgelerin bulunacağını garanti etmez; hata çubukları azaltılmış χ² ile yeniden ölçeklenmez. Süre/biçim belirsizliği, yıldız lekeleri, alet sistematikleri, zaman damgası hataları ve eksik fizik bu aralıkta kapsanmaz. Zayıf, kenarda kalan veya yeterli ön/son temel çizgisi olmayan olaylar reddedilir.','The 61-point centre grid refines up to four local minima; multiple sampled solutions or a more complex profile reject the fit. Formal centre intervals use the first Δχ²=1 crossing found in 64 steps per direction; sampled re-entry into a confidence region rejects the fit. Finite scan resolution does not guarantee finding every arbitrarily narrow region; source errors are not rescaled by reduced χ². Duration/shape uncertainty, stellar spots, instrument systematics, timestamp errors and missing physics are not included. Weak, edge-constrained or inadequately bracketed events are rejected.')+'

NASA / STScI MAST · Kepler · Python reference ↓ · README ↓

' return h def pages(root): rows=[] for en in(False,True): row={'key':'enobservationlab'if en else'observationlab','slug':'observation-analysis'if en else'gozlem-analiz-atolyesi','path':'/en/observation-analysis/'if en else'/gozlem-analiz-atolyesi/','lang':'en'if en else'tr','title':'Observation Analysis Workshop'if en else'Gözlem Analiz Atölyesi','description':'Measure an actual archived Kepler transit or a bounded light curve with a source-bound conditional timing model.'if en else'Gerçek Kepler arşiv geçişini veya sınırlı ışık eğrisini, kaynak ve belirsizliği açık koşullu zaman modeliyle ölç.','content':component(root,en),'image':'{{STRC_ASSETS}}/earth.webp','keywords':'transit timing BJD TDB Kepler photometry measurement uncertainty bağımsız tekrar geçiş zamanı'} if en:row.update(parent='enhome',translationOf='observationlab') rows.append(row) return rows def build_assets(root,dist,plugin=None): root=Path(root);prepare_example(root);out=Path(dist)/'assets/measurement-10';out.mkdir(parents=True,exist_ok=True) for name in('kepler8-input.json','kepler8-reference.json'):shutil.copyfile(root/'content/measurement-10'/name,out/name) # Reference script is delivered as inert text, never executable web code. shutil.copyfile(Path(__file__),out/'reproduce.py.txt') (out/'README.txt').write_text('SpaceTRC10 conditional timing reference\n\nSave reproduce.py.txt as reproduce.py and kepler8-input.json in the same folder. Run:\npython3 reproduce.py kepler8-input.json\n\nPython 3 standard library only. Input dataset:15–2000 rows, BJD_TDB days, normalised relative flux and positive source error. The fixed template duration and ingress are assumptions. The returned profile interval is conditional, uses a 64-step scan per direction and is not a complete physical or systematic uncertainty. No network call, new observation, independent review, telescope request or discovery is performed.\n') if __name__=='__main__': import sys if len(sys.argv)>1: obj=json.loads(Path(sys.argv[1]).read_text());print(json.dumps(fit(obj['dataset'],obj['method']),indent=2))