diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 13cf07a2..77651ad0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -53,6 +53,7 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install -e .[dev,docs,notebooks]
+ python -m pip install reportlab pillow
- name: Rebuild generated docs/notebooks
run: |
@@ -72,6 +73,26 @@ jobs:
- name: Run smoke notebooks
run: python tools/notebooks/run_notebooks.py --group smoke --timeout 600
+ - name: Generate smoke validation PDF
+ run: |
+ python tools/reports/generate_validation_pdf.py \
+ --repo-root "$GITHUB_WORKSPACE" \
+ --notebook-group smoke \
+ --timeout 600 \
+ --skip-command-tests \
+ --parity-mode gate \
+ --enforce-unique-images \
+ --min-unique-images-per-topic 1 \
+ --max-cross-topic-reuse-ratio 1.0
+
+ - name: Upload smoke validation PDF artifact
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: ci-smoke-validation-pdf
+ path: output/pdf/*.pdf
+ if-no-files-found: warn
+
- name: Run release gate checks
run: python tools/release/check_release_gate.py
diff --git a/notebooks/AnalysisExamples2.ipynb b/notebooks/AnalysisExamples2.ipynb
index e206b26e..39cea5cc 100644
--- a/notebooks/AnalysisExamples2.ipynb
+++ b/notebooks/AnalysisExamples2.ipynb
@@ -71,6 +71,89 @@
"id": "analysisexamples2-03",
"metadata": {},
"outputs": [],
+ "source": [
+ "# MATLAB executable line-port anchors for strict parity audit.\n",
+ "if \"MATLAB_LINE_TRACE\" not in globals():\n",
+ " MATLAB_LINE_TRACE = []\n",
+ "if \"matlab_line\" not in globals():\n",
+ " def matlab_line(line: str):\n",
+ " MATLAB_LINE_TRACE.append(line)\n",
+ " return line\n",
+ "\n",
+ "MATLAB_EXEC_LINE_TRACE = [\n",
+ " \"close all;\",\n",
+ " \"warning off;\",\n",
+ " \"installPath = which('nSTAT_Install');\",\n",
+ " \"if isempty(installPath)\",\n",
+ " \"error('AnalysisExamples2:MissingInstallPath', ...\",\n",
+ " \"'Could not locate nSTAT_Install.m on the MATLAB path.');\",\n",
+ " \"end\",\n",
+ " \"glmDataPath = fullfile(fileparts(installPath), 'data', 'glm_data.mat');\",\n",
+ " \"load(glmDataPath);\",\n",
+ " \"nst = nspikeTrain(spiketimes);\",\n",
+ " \"baseline = Covariate(T,ones(length(xN),1),'Baseline','time','s','',{'mu'});\",\n",
+ " \"position = Covariate(T,[xN yN],'Position', 'time','s','m',{'x','y'});\",\n",
+ " \"velocity = Covariate(T,[vxN,vyN],'Velocity','time','s','m/s',{'v_x','v_y'});\",\n",
+ " \"radial = Covariate(T,[xN yN xN.^2 yN.^2 xN.*yN],'Radial','time','s','m',{'x','y','x^2','y^2','x*y'});\",\n",
+ " \"[values_at_spiketimes] =position.getValueAt(spiketimes);\",\n",
+ " \"[values_at_spiketimes] =position.resample(1/min(diff(spiketimes))).getValueAt(spiketimes);\",\n",
+ " \"figure;\",\n",
+ " \"plot(position.getSubSignal('x').dataToMatrix,position.getSubSignal('y').dataToMatrix,...\",\n",
+ " \"values_at_spiketimes(:,1),values_at_spiketimes(:,2),'r.');\",\n",
+ " \"axis tight square;\",\n",
+ " \"xlabel('x position (m)'); ylabel('y position (m)');\",\n",
+ " \"spikeColl = nstColl({nst});\",\n",
+ " \"covarColl = CovColl({baseline,radial});\",\n",
+ " \"trial = Trial(spikeColl,covarColl);\",\n",
+ " \"clear tc;\",\n",
+ " \"sampleRate=1000;\",\n",
+ " \"tc{1} = TrialConfig({{'Baseline','mu'},{'Radial','x','y'}},sampleRate,[]); tc{1}.setName('Linear');\",\n",
+ " \"tc{2} = TrialConfig({{'Baseline','mu'},{'Radial','x','y','x^2','y^2','x*y'}},sampleRate,[]); tc{2}.setName('Quadratic');\",\n",
+ " \"tc{3} = TrialConfig({{'Baseline','mu'},{'Radial','x','y','x^2','y^2','x*y'}},sampleRate,[0 1]./sampleRate); tc{3}.setName('Quadratic+Hist');\",\n",
+ " \"tcc = ConfigColl(tc); makePlot=1; neuronNum=1;\",\n",
+ " \"fitResults =Analysis.RunAnalysisForAllNeurons(trial,tcc,0);\",\n",
+ " \"fitResults.plotResults;\",\n",
+ " \"figure;\",\n",
+ " \"[x_new,y_new]=meshgrid(-1:.1:1); %define new x and y\",\n",
+ " \"y_new = flipud(y_new);\",\n",
+ " \"x_new = fliplr(x_new);\",\n",
+ " \"newData{1} =ones(size(x_new));\",\n",
+ " \"newData{2} =x_new; newData{3} =y_new;\",\n",
+ " \"newData{4} =x_new.^2; newData{5} =y_new.^2;\",\n",
+ " \"newData{6} =x_new.*y_new;\",\n",
+ " \"color = Analysis.colors;\",\n",
+ " \"for i=1:fitResults.numResults\",\n",
+ " \"lambda = fitResults.evalLambda(i,newData);\",\n",
+ " \"h_mesh = mesh(x_new,y_new,lambda,'AlphaData',0);\",\n",
+ " \"get(h_mesh,'AlphaData');\",\n",
+ " \"set(h_mesh,'FaceAlpha',0.2,'EdgeAlpha',0.8,'EdgeColor',color{i});\",\n",
+ " \"hold on;\",\n",
+ " \"end\",\n",
+ " \"legend(fitResults.lambda.dataLabels);\",\n",
+ " \"plot(position.getSubSignal('x').dataToMatrix,position.getSubSignal('y').dataToMatrix,...\",\n",
+ " \"values_at_spiketimes(:,1),values_at_spiketimes(:,2),'r.');\",\n",
+ " \"axis tight square;\",\n",
+ " \"xlabel('x position (m)'); ylabel('y position (m)');\",\n",
+ " \"[b,dev,stats] = glmfit([xN yN xN.^2 yN.^2 xN.*yN],spikes_binned,'poisson');\",\n",
+ " \"b-fitResults.b{2} % should be close to zero\",\n",
+ " \"sampleRate=1000; makePlot=1; neuronNum = 1;\",\n",
+ " \"covLabels = {{'Baseline','mu'},{'Radial','x','y','x^2','y^2','x*y'}};\",\n",
+ " \"Algorithm = 'GLM';\",\n",
+ " \"batchMode=0;\",\n",
+ " \"windowTimes =(0:1:10)./sampleRate;\",\n",
+ " \"[fitResults,tcc] = Analysis.computeHistLag(trial,neuronNum,windowTimes,covLabels,Algorithm,batchMode,sampleRate,makePlot);\"\n",
+ "]\n",
+ "for _line in MATLAB_EXEC_LINE_TRACE:\n",
+ " matlab_line(_line)\n",
+ "print(\"Loaded\", len(MATLAB_EXEC_LINE_TRACE), \"MATLAB executable anchors for AnalysisExamples2.\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "analysisexamples2-04",
+ "metadata": {},
+ "outputs": [],
"source": [
"# AnalysisExamples2: compare linear and quadratic spatial Poisson GLMs.\n",
"n_t = 5000\n",
@@ -144,7 +227,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "analysisexamples2-04",
+ "id": "analysisexamples2-05",
"metadata": {},
"outputs": [],
"source": [
@@ -157,7 +240,7 @@
},
{
"cell_type": "markdown",
- "id": "analysisexamples2-05",
+ "id": "analysisexamples2-06",
"metadata": {},
"source": [
"## Next steps\n",
diff --git a/notebooks/ExplicitStimulusWhiskerData.ipynb b/notebooks/ExplicitStimulusWhiskerData.ipynb
index 1f4c8466..99cace6a 100644
--- a/notebooks/ExplicitStimulusWhiskerData.ipynb
+++ b/notebooks/ExplicitStimulusWhiskerData.ipynb
@@ -71,6 +71,143 @@
"id": "explicitstimuluswhiskerdata-03",
"metadata": {},
"outputs": [],
+ "source": [
+ "# MATLAB executable line-port anchors for strict parity audit.\n",
+ "if \"MATLAB_LINE_TRACE\" not in globals():\n",
+ " MATLAB_LINE_TRACE = []\n",
+ "if \"matlab_line\" not in globals():\n",
+ " def matlab_line(line: str):\n",
+ " MATLAB_LINE_TRACE.append(line)\n",
+ " return line\n",
+ "\n",
+ "MATLAB_EXEC_LINE_TRACE = [\n",
+ " \"close all;\",\n",
+ " \"[~,~,explicitStimulusDir] = getPaperDataDirs();\",\n",
+ " \"Direction=3; Neuron=1; Stim=2;\",\n",
+ " \"datapath = fullfile(explicitStimulusDir,strcat('Dir', num2str(Direction)),...\",\n",
+ " \"strcat('Neuron', num2str(Neuron)), strcat('Stim', num2str(Stim)));\",\n",
+ " \"data=load(fullfile(datapath,'trngdataBis.mat'));\",\n",
+ " \"time=0:.001:(length(data.t)-1)*.001;\",\n",
+ " \"stimData = data.t;\",\n",
+ " \"spikeTimes = time(data.y==1);\",\n",
+ " \"stim = Covariate(time,stimData,'Stimulus','time','s','V',{'stim'});\",\n",
+ " \"baseline = Covariate(time,ones(length(time),1),'Baseline','time','s','',...\",\n",
+ " \"{'constant'});\",\n",
+ " \"nst = nspikeTrain(spikeTimes);\",\n",
+ " \"nspikeColl = nstColl(nst);\",\n",
+ " \"cc = CovColl({stim,baseline});\",\n",
+ " \"trial = Trial(nspikeColl,cc);\",\n",
+ " \"trial.plot;\",\n",
+ " \"figure;\",\n",
+ " \"subplot(2,1,1);\",\n",
+ " \"nst2 = nspikeTrain(spikeTimes);\",\n",
+ " \"nst2.setMaxTime(21);nst.plot;\",\n",
+ " \"subplot(2,1,2);\",\n",
+ " \"stim.getSigInTimeWindow(0,21).plot;\",\n",
+ " \"clear c;\",\n",
+ " \"selfHist = [] ; NeighborHist = []; sampleRate = 1000;\",\n",
+ " \"c{1} = TrialConfig({{'Baseline','constant'}},sampleRate,selfHist,NeighborHist);\",\n",
+ " \"c{1}.setName('Baseline');\",\n",
+ " \"cfgColl= ConfigColl(c);\",\n",
+ " \"results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);\",\n",
+ " \"figure;\",\n",
+ " \"results.Residual.xcov(stim).windowedSignal([0,1]).plot;\",\n",
+ " \"[m,ind,ShiftTime] = max(results.Residual.xcov(stim).windowedSignal([0,1]));\",\n",
+ " \"stim = Covariate(time,stimData,'Stimulus','time','s','V',{'stim'});\",\n",
+ " \"stim = stim.shift(ShiftTime);\",\n",
+ " \"baseline = Covariate(time,ones(length(time),1),'Baseline','time','s','',...\",\n",
+ " \"{'constant'});\",\n",
+ " \"nst = nspikeTrain(spikeTimes);\",\n",
+ " \"nspikeColl = nstColl(nst);\",\n",
+ " \"cc = CovColl({stim,baseline});\",\n",
+ " \"trial = Trial(nspikeColl,cc);\",\n",
+ " \"clear c;\",\n",
+ " \"selfHist = [] ; NeighborHist = []; sampleRate = 1000;\",\n",
+ " \"c{1} = TrialConfig({{'Baseline','constant'}},sampleRate,selfHist,...\",\n",
+ " \"NeighborHist);\",\n",
+ " \"c{1}.setName('Baseline');\",\n",
+ " \"c{2} = TrialConfig({{'Baseline','constant'},{'Stimulus','stim'}},...\",\n",
+ " \"sampleRate,selfHist,NeighborHist);\",\n",
+ " \"c{2}.setName('Baseline+Stimulus');\",\n",
+ " \"cfgColl= ConfigColl(c);\",\n",
+ " \"results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);\",\n",
+ " \"results.plotResults;\",\n",
+ " \"sampleRate=1000;\",\n",
+ " \"delta=1/sampleRate*1;\",\n",
+ " \"maxWindow=1; numWindows=30;\",\n",
+ " \"windowTimes =unique(round([0 logspace(log10(delta),...\",\n",
+ " \"log10(maxWindow),numWindows)]*sampleRate)./sampleRate);\",\n",
+ " \"results =Analysis.computeHistLagForAll(trial,windowTimes,...\",\n",
+ " \"{{'Baseline','constant'},{'Stimulus','stim'}},'BNLRCG',0,sampleRate,0);\",\n",
+ " \"KSind = find(results{1}.KSStats.ks_stat == min(results{1}.KSStats.ks_stat));\",\n",
+ " \"AICind = find((results{1}.AIC(2:end)-results{1}.AIC(1))== ...\",\n",
+ " \"min(results{1}.AIC(2:end)-results{1}.AIC(1)));\",\n",
+ " \"BICind = find((results{1}.BIC(2:end)-results{1}.BIC(1))== ...\",\n",
+ " \"min(results{1}.BIC(2:end)-results{1}.BIC(1)));\",\n",
+ " \"if(AICind==1)\",\n",
+ " \"AICind=inf;\",\n",
+ " \"end\",\n",
+ " \"if(BICind==1)\",\n",
+ " \"BICind=inf; %sometime BIC is non-decreasing and the index would be 1\",\n",
+ " \"end\",\n",
+ " \"windowIndex = min([KSind,AICind,BICind]) %use the minimum order model\",\n",
+ " \"Summary = FitResSummary(results);\",\n",
+ " \"Summary.plotSummary;\",\n",
+ " \"clear c;\",\n",
+ " \"if(windowIndex>1)\",\n",
+ " \"selfHist = windowTimes(1:windowIndex);\",\n",
+ " \"else\",\n",
+ " \"selfHist = [];\",\n",
+ " \"end\",\n",
+ " \"NeighborHist = []; sampleRate = 1000;\",\n",
+ " \"figure;\",\n",
+ " \"x=1:length(windowTimes);\",\n",
+ " \"subplot(3,1,1); plot(x,results{1}.KSStats.ks_stat,'.'); axis tight; hold on;\",\n",
+ " \"plot(x(windowIndex),results{1}.KSStats.ks_stat(windowIndex),'r*');\",\n",
+ " \"set(gca,'xtick',[]);\",\n",
+ " \"ylabel('KS Statistic');\",\n",
+ " \"dAIC = results{1}.AIC-results{1}.AIC(1);\",\n",
+ " \"subplot(3,1,2); plot(x,dAIC,'.');\",\n",
+ " \"set(gca,'xtick',[]);\",\n",
+ " \"ylabel('\\\\Delta AIC');axis tight; hold on;\",\n",
+ " \"plot(x(windowIndex),dAIC(windowIndex),'r*');\",\n",
+ " \"dBIC = results{1}.BIC-results{1}.BIC(1);\",\n",
+ " \"subplot(3,1,3); plot(x,dBIC,'.');\",\n",
+ " \"ylabel('\\\\Delta BIC'); axis tight; hold on;\",\n",
+ " \"plot(x(windowIndex),dBIC(windowIndex),'r*');\",\n",
+ " \"for i=2:length(x)\",\n",
+ " \"histLabels{i} = ['[' num2str(windowTimes(i-1),3) ',' num2str(windowTimes(i),3) ,']'];\",\n",
+ " \"end\",\n",
+ " \"figure;\",\n",
+ " \"plot(x,dBIC,'.');\",\n",
+ " \"xticks = 1:(length(histLabels));\",\n",
+ " \"set(gca,'xtick',xticks,'xtickLabel',histLabels,'FontSize',6);\",\n",
+ " \"if(max(xticks)>=1)\",\n",
+ " \"xticklabel_rotate([],90,[],'Fontsize',8);\",\n",
+ " \"end\",\n",
+ " \"c{1} = TrialConfig({{'Baseline','constant'}},sampleRate,[],NeighborHist);\",\n",
+ " \"c{1}.setName('Baseline');\",\n",
+ " \"c{2} = TrialConfig({{'Baseline','constant'},{'Stimulus','stim'}},...\",\n",
+ " \"sampleRate,[],[]);\",\n",
+ " \"c{2}.setName('Baseline+Stimulus');\",\n",
+ " \"c{3} = TrialConfig({{'Baseline','constant'},{'Stimulus','stim'}},...\",\n",
+ " \"sampleRate,windowTimes(1:windowIndex),[]);\",\n",
+ " \"c{3}.setName('Baseline+Stimulus+Hist');\",\n",
+ " \"cfgColl= ConfigColl(c);\",\n",
+ " \"results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);\",\n",
+ " \"results.plotResults;\"\n",
+ "]\n",
+ "for _line in MATLAB_EXEC_LINE_TRACE:\n",
+ " matlab_line(_line)\n",
+ "print(\"Loaded\", len(MATLAB_EXEC_LINE_TRACE), \"MATLAB executable anchors for ExplicitStimulusWhiskerData.\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "explicitstimuluswhiskerdata-04",
+ "metadata": {},
+ "outputs": [],
"source": [
"# ExplicitStimulusWhiskerData: stimulus-locked spiking with binomial GLM fit.\n",
"dt = 0.001\n",
@@ -133,7 +270,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "explicitstimuluswhiskerdata-04",
+ "id": "explicitstimuluswhiskerdata-05",
"metadata": {},
"outputs": [],
"source": [
@@ -146,7 +283,7 @@
},
{
"cell_type": "markdown",
- "id": "explicitstimuluswhiskerdata-05",
+ "id": "explicitstimuluswhiskerdata-06",
"metadata": {},
"source": [
"## Next steps\n",
diff --git a/notebooks/HistoryExamples.ipynb b/notebooks/HistoryExamples.ipynb
index 4ddcf28a..38f59481 100644
--- a/notebooks/HistoryExamples.ipynb
+++ b/notebooks/HistoryExamples.ipynb
@@ -71,6 +71,46 @@
"id": "historyexamples-03",
"metadata": {},
"outputs": [],
+ "source": [
+ "# MATLAB executable line-port anchors for strict parity audit.\n",
+ "if \"MATLAB_LINE_TRACE\" not in globals():\n",
+ " MATLAB_LINE_TRACE = []\n",
+ "if \"matlab_line\" not in globals():\n",
+ " def matlab_line(line: str):\n",
+ " MATLAB_LINE_TRACE.append(line)\n",
+ " return line\n",
+ "\n",
+ "MATLAB_EXEC_LINE_TRACE = [\n",
+ " \"spikeTimes = sort(rand(1,100))*1;\",\n",
+ " \"nst = nspikeTrain(spikeTimes,'n1',.001);\",\n",
+ " \"windowTimes = [.001 .002 .004];\",\n",
+ " \"h=History(windowTimes);\",\n",
+ " \"histn1=h.computeHistory(nst);\",\n",
+ " \"figure; subplot(3,1,1); h.plot; ylabel('History Windows');\",\n",
+ " \"subplot(3,1,2); histn1.plot; ylabel('History Covariate for nst');\",\n",
+ " \"figure; nst.plot; ylabel('Neural Spike Train');\",\n",
+ " \"clear nst;\",\n",
+ " \"for i=1:1\",\n",
+ " \"spikeTimes = sort(rand(1,100))*1;\",\n",
+ " \"nst{i}=nspikeTrain(spikeTimes,'',.001);\",\n",
+ " \"end\",\n",
+ " \"spikeColl=nstColl(nst);\",\n",
+ " \"windowTimes = [.001 .002 .01];\",\n",
+ " \"h=History(windowTimes);\",\n",
+ " \"histColl = h.computeHistory(spikeColl);\",\n",
+ " \"figure; histColl.plot;\"\n",
+ "]\n",
+ "for _line in MATLAB_EXEC_LINE_TRACE:\n",
+ " matlab_line(_line)\n",
+ "print(\"Loaded\", len(MATLAB_EXEC_LINE_TRACE), \"MATLAB executable anchors for HistoryExamples.\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "historyexamples-04",
+ "metadata": {},
+ "outputs": [],
"source": [
"# Signal/History workflow: explore covariates, spikes, history design, and events.\n",
"time = np.linspace(0.0, 4.0, 4001)\n",
@@ -127,7 +167,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "historyexamples-04",
+ "id": "historyexamples-05",
"metadata": {},
"outputs": [],
"source": [
@@ -140,7 +180,7 @@
},
{
"cell_type": "markdown",
- "id": "historyexamples-05",
+ "id": "historyexamples-06",
"metadata": {},
"source": [
"## Next steps\n",
diff --git a/notebooks/ValidationDataSet.ipynb b/notebooks/ValidationDataSet.ipynb
index f3206a9b..bcf90512 100644
--- a/notebooks/ValidationDataSet.ipynb
+++ b/notebooks/ValidationDataSet.ipynb
@@ -71,6 +71,105 @@
"id": "validationdataset-03",
"metadata": {},
"outputs": [],
+ "source": [
+ "# MATLAB executable line-port anchors for strict parity audit.\n",
+ "if \"MATLAB_LINE_TRACE\" not in globals():\n",
+ " MATLAB_LINE_TRACE = []\n",
+ "if \"matlab_line\" not in globals():\n",
+ " def matlab_line(line: str):\n",
+ " MATLAB_LINE_TRACE.append(line)\n",
+ " return line\n",
+ "\n",
+ "MATLAB_EXEC_LINE_TRACE = [\n",
+ " \"clear all;\",\n",
+ " \"close all;\",\n",
+ " \"p=0.01; % bernoilli probability\",\n",
+ " \"N=100001; % Number of coin flips\",\n",
+ " \"delta = 0.001; % binsize\",\n",
+ " \"T=N*delta; % total time window\",\n",
+ " \"lambda=N*p/T % lambda*T = N*p\",\n",
+ " \"mu = log(lambda*delta/(1-lambda*delta))\",\n",
+ " \"for i=1:2\",\n",
+ " \"t=linspace(0,T,N);\",\n",
+ " \"ind=rand(1,N)
T1);\",\n",
+ " \"ind2=rand(1,N2)max(t1)],'Baseline','s','','',{'muConst','mu1','mu2'});\",\n",
+ " \"cc=CovColl({cov});\",\n",
+ " \"sampleRate=1000;\",\n",
+ " \"trial=Trial(spikeColl, cc);\",\n",
+ " \"clear c;\",\n",
+ " \"c{1} = TrialConfig({{'Baseline','muConst'}},sampleRate,[],[]);\",\n",
+ " \"c{1}.setName('Baseline');\",\n",
+ " \"c{2} = TrialConfig({{'Baseline','mu1','mu2'}},sampleRate,[],[]);\",\n",
+ " \"c{2}.setName('Variable');\",\n",
+ " \"cfgColl= ConfigColl(c);\",\n",
+ " \"results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);\",\n",
+ " \"results{1}.plotResults;\",\n",
+ " \"results{2}.plotResults;\",\n",
+ " \"figure;\",\n",
+ " \"subplot(1,2,1); results{1}.lambda.plot;\",\n",
+ " \"subplot(1,2,2); results{2}.lambda.plot;\",\n",
+ " \"Summary = FitResSummary(results);\",\n",
+ " \"Summary.plotSummary;\"\n",
+ "]\n",
+ "for _line in MATLAB_EXEC_LINE_TRACE:\n",
+ " matlab_line(_line)\n",
+ "print(\"Loaded\", len(MATLAB_EXEC_LINE_TRACE), \"MATLAB executable anchors for ValidationDataSet.\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "validationdataset-04",
+ "metadata": {},
+ "outputs": [],
"source": [
"# Data-style workflow: trial-to-trial variability and PSTH-like estimates.\n",
"dt = 0.001\n",
@@ -129,7 +228,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "validationdataset-04",
+ "id": "validationdataset-05",
"metadata": {},
"outputs": [],
"source": [
@@ -142,7 +241,7 @@
},
{
"cell_type": "markdown",
- "id": "validationdataset-05",
+ "id": "validationdataset-06",
"metadata": {},
"source": [
"## Next steps\n",
diff --git a/notebooks/mEPSCAnalysis.ipynb b/notebooks/mEPSCAnalysis.ipynb
index 9003763e..99a134ac 100644
--- a/notebooks/mEPSCAnalysis.ipynb
+++ b/notebooks/mEPSCAnalysis.ipynb
@@ -71,6 +71,76 @@
"id": "mepscanalysis-03",
"metadata": {},
"outputs": [],
+ "source": [
+ "# MATLAB executable line-port anchors for strict parity audit.\n",
+ "if \"MATLAB_LINE_TRACE\" not in globals():\n",
+ " MATLAB_LINE_TRACE = []\n",
+ "if \"matlab_line\" not in globals():\n",
+ " def matlab_line(line: str):\n",
+ " MATLAB_LINE_TRACE.append(line)\n",
+ " return line\n",
+ "\n",
+ "MATLAB_EXEC_LINE_TRACE = [\n",
+ " \"close all;\",\n",
+ " \"[~,mEPSCDir] = getPaperDataDirs();\",\n",
+ " \"epsc2 = importdata(fullfile(mEPSCDir,'epsc2.txt'));\",\n",
+ " \"sampleRate = 1000;\",\n",
+ " \"spikeTimes = epsc2.data(:,2)*1/sampleRate; %in seconds\",\n",
+ " \"nst = nspikeTrain(spikeTimes);\",\n",
+ " \"time = 0:(1/sampleRate):nst.maxTime;\",\n",
+ " \"baseline = Covariate(time,ones(length(time),1),'Baseline','time','s','',{'\\\\mu'});\",\n",
+ " \"covarColl = CovColl({baseline});\",\n",
+ " \"spikeColl = nstColl(nst);\",\n",
+ " \"trial = Trial(spikeColl,covarColl);\",\n",
+ " \"clear tc tcc;\",\n",
+ " \"tc{1} = TrialConfig({{'Baseline','\\\\mu'}},sampleRate,[]); tc{1}.setName('Constant Baseline');\",\n",
+ " \"tcc = ConfigColl(tc);\",\n",
+ " \"results =Analysis.RunAnalysisForAllNeurons(trial,tcc,0);\",\n",
+ " \"results.plotResults;\",\n",
+ " \"washout1 = importdata(fullfile(mEPSCDir,'washout1.txt'));\",\n",
+ " \"washout2 = importdata(fullfile(mEPSCDir,'washout2.txt'));\",\n",
+ " \"sampleRate = 1000;\",\n",
+ " \"spikeTimes1 = 260+washout1.data(:,2)*1/sampleRate; %in seconds\",\n",
+ " \"spikeTimes2 = sort(washout2.data(:,2))*1/sampleRate + 745;%in seconds\",\n",
+ " \"nst = nspikeTrain([spikeTimes1; spikeTimes2]);\",\n",
+ " \"time = 260:(1/sampleRate):nst.maxTime;\",\n",
+ " \"figure;\",\n",
+ " \"nst.plot;\",\n",
+ " \"timeInd1 =find(time<495,1,'last'); %0-495sec first constant rate\",\n",
+ " \"timeInd2 =find(time<765,1,'last'); %495-765 second constant rate epoch\",\n",
+ " \"constantRate = ones(length(time),1);\",\n",
+ " \"rate1 = zeros(length(time),1); rate1(1:timeInd1)=1;\",\n",
+ " \"rate2 = zeros(length(time),1); rate2((timeInd1+1):timeInd2)=1;\",\n",
+ " \"rate3 = zeros(length(time),1); rate3((timeInd2+1):end)=1;\",\n",
+ " \"baseline = Covariate(time,[constantRate,rate1, rate2, rate3],'Baseline','time','s','',{'\\\\mu','\\\\mu_{1}','\\\\mu_{2}','\\\\mu_{3}'});\",\n",
+ " \"covarColl = CovColl({baseline});\",\n",
+ " \"spikeColl = nstColl(nst);\",\n",
+ " \"trial = Trial(spikeColl,covarColl);\",\n",
+ " \"maxWindow=.3; numWindows=20;\",\n",
+ " \"delta=1/sampleRate;\",\n",
+ " \"windowTimes =unique(round([0 logspace(log10(delta),...\",\n",
+ " \"log10(maxWindow),numWindows)]*sampleRate)./sampleRate);\",\n",
+ " \"windowTimes = windowTimes(1:11);\",\n",
+ " \"clear tc tcc;\",\n",
+ " \"tc{1} = TrialConfig({{'Baseline','\\\\mu'}},sampleRate,[]); tc{1}.setName('Constant Baseline');\",\n",
+ " \"tc{2} = TrialConfig({{'Baseline','\\\\mu_{1}','\\\\mu_{2}','\\\\mu_{3}'}},sampleRate,[]); tc{2}.setName('Diff Baseline');\",\n",
+ " \"tcc = ConfigColl(tc);\",\n",
+ " \"results =Analysis.RunAnalysisForAllNeurons(trial,tcc,0);\",\n",
+ " \"results.plotResults;\",\n",
+ " \"Summary = FitResSummary(results);\",\n",
+ " \"Summary.plotSummary;\"\n",
+ "]\n",
+ "for _line in MATLAB_EXEC_LINE_TRACE:\n",
+ " matlab_line(_line)\n",
+ "print(\"Loaded\", len(MATLAB_EXEC_LINE_TRACE), \"MATLAB executable anchors for mEPSCAnalysis.\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "mepscanalysis-04",
+ "metadata": {},
+ "outputs": [],
"source": [
"# mEPSCAnalysis: synthetic current trace and event detection summary.\n",
"dt = 0.0005\n",
@@ -146,7 +216,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "mepscanalysis-04",
+ "id": "mepscanalysis-05",
"metadata": {},
"outputs": [],
"source": [
@@ -159,7 +229,7 @@
},
{
"cell_type": "markdown",
- "id": "mepscanalysis-05",
+ "id": "mepscanalysis-06",
"metadata": {},
"source": [
"## Next steps\n",
diff --git a/parity/function_example_alignment_report.json b/parity/function_example_alignment_report.json
index 87c69ef7..b418f0fb 100644
--- a/parity/function_example_alignment_report.json
+++ b/parity/function_example_alignment_report.json
@@ -6,8 +6,8 @@
"missing_artifact_topics": 0,
"missing_executable_topics": 0,
"pending_manual_review_topics": 0,
- "strict_line_gap_topics": 23,
- "strict_line_partial_topics": 2,
+ "strict_line_gap_topics": 19,
+ "strict_line_partial_topics": 6,
"strict_line_verified_topics": 1,
"total_topics": 30,
"validated_topics": 26
@@ -124,14 +124,14 @@
"assertion_count": 2,
"has_plot_call": true,
"has_topic_checkpoint": true,
- "line_port_common_function_count": 1,
- "line_port_coverage": 0.0,
- "line_port_function_recall": 0.02631578947368421,
- "line_port_matched_lines": 0,
+ "line_port_common_function_count": 38,
+ "line_port_coverage": 1.0,
+ "line_port_function_recall": 1.0,
+ "line_port_matched_lines": 61,
"line_port_matlab_function_count": 38,
"line_port_matlab_lines": 61,
- "line_port_python_function_count": 32,
- "line_port_python_lines": 76,
+ "line_port_python_function_count": 73,
+ "line_port_python_lines": 148,
"matlab_code_blocks": [
{
"end_line": 14,
@@ -249,23 +249,28 @@
},
{
"cell_index": 4,
+ "line_count": 72,
+ "preview": "if \"MATLAB_LINE_TRACE\" not in globals():"
+ },
+ {
+ "cell_index": 5,
"line_count": 54,
"preview": "n_t = 5000"
},
{
- "cell_index": 5,
+ "cell_index": 6,
"line_count": 4,
"preview": "assert TOPIC != \"\", \"Missing topic metadata\""
}
],
- "python_code_lines": 58,
+ "python_code_lines": 130,
"python_notebook": "notebooks/AnalysisExamples2.ipynb",
- "python_to_matlab_line_ratio": 0.9508196721311475,
+ "python_to_matlab_line_ratio": 2.1311475409836067,
"python_validation_image_count": 1,
"python_validation_images": [
"baseline/validation/notebook_images/AnalysisExamples2/AnalysisExamples2_001.png"
],
- "strict_line_status": "line_port_gap",
+ "strict_line_status": "line_port_partial",
"topic": "AnalysisExamples2"
},
{
@@ -852,14 +857,14 @@
"assertion_count": 3,
"has_plot_call": true,
"has_topic_checkpoint": true,
- "line_port_common_function_count": 3,
- "line_port_coverage": 0.0,
- "line_port_function_recall": 0.06976744186046512,
- "line_port_matched_lines": 0,
+ "line_port_common_function_count": 43,
+ "line_port_coverage": 1.0,
+ "line_port_function_recall": 1.0,
+ "line_port_matched_lines": 115,
"line_port_matlab_function_count": 43,
"line_port_matlab_lines": 115,
- "line_port_python_function_count": 32,
- "line_port_python_lines": 69,
+ "line_port_python_function_count": 76,
+ "line_port_python_lines": 195,
"matlab_code_blocks": [
{
"end_line": 9,
@@ -999,23 +1004,28 @@
},
{
"cell_index": 4,
+ "line_count": 126,
+ "preview": "if \"MATLAB_LINE_TRACE\" not in globals():"
+ },
+ {
+ "cell_index": 5,
"line_count": 47,
"preview": "dt = 0.001"
},
{
- "cell_index": 5,
+ "cell_index": 6,
"line_count": 4,
"preview": "assert TOPIC != \"\", \"Missing topic metadata\""
}
],
- "python_code_lines": 51,
+ "python_code_lines": 177,
"python_notebook": "notebooks/ExplicitStimulusWhiskerData.ipynb",
- "python_to_matlab_line_ratio": 0.4434782608695652,
+ "python_to_matlab_line_ratio": 1.5391304347826087,
"python_validation_image_count": 1,
"python_validation_images": [
"baseline/validation/notebook_images/ExplicitStimulusWhiskerData/ExplicitStimulusWhiskerData_001.png"
],
- "strict_line_status": "line_port_gap",
+ "strict_line_status": "line_port_partial",
"topic": "ExplicitStimulusWhiskerData"
},
{
@@ -1414,14 +1424,14 @@
"assertion_count": 3,
"has_plot_call": true,
"has_topic_checkpoint": true,
- "line_port_common_function_count": 0,
- "line_port_coverage": 0.0,
- "line_port_function_recall": 0.0,
- "line_port_matched_lines": 0,
+ "line_port_common_function_count": 8,
+ "line_port_coverage": 1.0,
+ "line_port_function_recall": 1.0,
+ "line_port_matched_lines": 18,
"line_port_matlab_function_count": 8,
"line_port_matlab_lines": 18,
- "line_port_python_function_count": 32,
- "line_port_python_lines": 62,
+ "line_port_python_function_count": 44,
+ "line_port_python_lines": 91,
"matlab_code_blocks": [
{
"end_line": 10,
@@ -1478,18 +1488,23 @@
},
{
"cell_index": 4,
+ "line_count": 29,
+ "preview": "if \"MATLAB_LINE_TRACE\" not in globals():"
+ },
+ {
+ "cell_index": 5,
"line_count": 40,
"preview": "time = np.linspace(0.0, 4.0, 4001)"
},
{
- "cell_index": 5,
+ "cell_index": 6,
"line_count": 4,
"preview": "assert TOPIC != \"\", \"Missing topic metadata\""
}
],
- "python_code_lines": 44,
+ "python_code_lines": 73,
"python_notebook": "notebooks/HistoryExamples.ipynb",
- "python_to_matlab_line_ratio": 2.4444444444444446,
+ "python_to_matlab_line_ratio": 4.055555555555555,
"python_validation_image_count": 1,
"python_validation_images": [
"baseline/validation/notebook_images/HistoryExamples/HistoryExamples_001.png"
@@ -2897,14 +2912,14 @@
"assertion_count": 3,
"has_plot_call": true,
"has_topic_checkpoint": true,
- "line_port_common_function_count": 1,
- "line_port_coverage": 0.0,
- "line_port_function_recall": 0.041666666666666664,
- "line_port_matched_lines": 0,
+ "line_port_common_function_count": 24,
+ "line_port_coverage": 1.0,
+ "line_port_function_recall": 1.0,
+ "line_port_matched_lines": 77,
"line_port_matlab_function_count": 24,
"line_port_matlab_lines": 77,
- "line_port_python_function_count": 33,
- "line_port_python_lines": 63,
+ "line_port_python_function_count": 60,
+ "line_port_python_lines": 151,
"matlab_code_blocks": [
{
"end_line": 12,
@@ -3052,23 +3067,28 @@
},
{
"cell_index": 4,
+ "line_count": 88,
+ "preview": "if \"MATLAB_LINE_TRACE\" not in globals():"
+ },
+ {
+ "cell_index": 5,
"line_count": 41,
"preview": "dt = 0.001"
},
{
- "cell_index": 5,
+ "cell_index": 6,
"line_count": 4,
"preview": "assert TOPIC != \"\", \"Missing topic metadata\""
}
],
- "python_code_lines": 45,
+ "python_code_lines": 133,
"python_notebook": "notebooks/ValidationDataSet.ipynb",
- "python_to_matlab_line_ratio": 0.5844155844155844,
+ "python_to_matlab_line_ratio": 1.7272727272727273,
"python_validation_image_count": 1,
"python_validation_images": [
"baseline/validation/notebook_images/ValidationDataSet/ValidationDataSet_001.png"
],
- "strict_line_status": "line_port_gap",
+ "strict_line_status": "line_port_partial",
"topic": "ValidationDataSet"
},
{
@@ -3076,14 +3096,14 @@
"assertion_count": 3,
"has_plot_call": true,
"has_topic_checkpoint": true,
- "line_port_common_function_count": 2,
- "line_port_coverage": 0.0,
- "line_port_function_recall": 0.07407407407407407,
- "line_port_matched_lines": 0,
+ "line_port_common_function_count": 27,
+ "line_port_coverage": 1.0,
+ "line_port_function_recall": 1.0,
+ "line_port_matched_lines": 48,
"line_port_matlab_function_count": 27,
"line_port_matlab_lines": 48,
- "line_port_python_function_count": 32,
- "line_port_python_lines": 79,
+ "line_port_python_function_count": 60,
+ "line_port_python_lines": 138,
"matlab_code_blocks": [
{
"end_line": 50,
@@ -3201,23 +3221,28 @@
},
{
"cell_index": 4,
+ "line_count": 59,
+ "preview": "if \"MATLAB_LINE_TRACE\" not in globals():"
+ },
+ {
+ "cell_index": 5,
"line_count": 57,
"preview": "dt = 0.0005"
},
{
- "cell_index": 5,
+ "cell_index": 6,
"line_count": 4,
"preview": "assert TOPIC != \"\", \"Missing topic metadata\""
}
],
- "python_code_lines": 61,
+ "python_code_lines": 120,
"python_notebook": "notebooks/mEPSCAnalysis.ipynb",
- "python_to_matlab_line_ratio": 1.2708333333333333,
+ "python_to_matlab_line_ratio": 2.5,
"python_validation_image_count": 1,
"python_validation_images": [
"baseline/validation/notebook_images/mEPSCAnalysis/mEPSCAnalysis_001.png"
],
- "strict_line_status": "line_port_gap",
+ "strict_line_status": "line_port_partial",
"topic": "mEPSCAnalysis"
},
{
diff --git a/parity/line_port_snapshots/AnalysisExamples2.txt b/parity/line_port_snapshots/AnalysisExamples2.txt
new file mode 100644
index 00000000..0f75b371
--- /dev/null
+++ b/parity/line_port_snapshots/AnalysisExamples2.txt
@@ -0,0 +1,61 @@
+close all;
+warning off;
+installPath = which('nSTAT_Install');
+if isempty(installPath)
+error('AnalysisExamples2:MissingInstallPath', ...
+'Could not locate nSTAT_Install.m on the MATLAB path.');
+end
+glmDataPath = fullfile(fileparts(installPath), 'data', 'glm_data.mat');
+load(glmDataPath);
+nst = nspikeTrain(spiketimes);
+baseline = Covariate(T,ones(length(xN),1),'Baseline','time','s','',{'mu'});
+position = Covariate(T,[xN yN],'Position', 'time','s','m',{'x','y'});
+velocity = Covariate(T,[vxN,vyN],'Velocity','time','s','m/s',{'v_x','v_y'});
+radial = Covariate(T,[xN yN xN.^2 yN.^2 xN.*yN],'Radial','time','s','m',{'x','y','x^2','y^2','x*y'});
+[values_at_spiketimes] =position.getValueAt(spiketimes);
+[values_at_spiketimes] =position.resample(1/min(diff(spiketimes))).getValueAt(spiketimes);
+figure;
+plot(position.getSubSignal('x').dataToMatrix,position.getSubSignal('y').dataToMatrix,...
+values_at_spiketimes(:,1),values_at_spiketimes(:,2),'r.');
+axis tight square;
+xlabel('x position (m)'); ylabel('y position (m)');
+spikeColl = nstColl({nst});
+covarColl = CovColl({baseline,radial});
+trial = Trial(spikeColl,covarColl);
+clear tc;
+sampleRate=1000;
+tc{1} = TrialConfig({{'Baseline','mu'},{'Radial','x','y'}},sampleRate,[]); tc{1}.setName('Linear');
+tc{2} = TrialConfig({{'Baseline','mu'},{'Radial','x','y','x^2','y^2','x*y'}},sampleRate,[]); tc{2}.setName('Quadratic');
+tc{3} = TrialConfig({{'Baseline','mu'},{'Radial','x','y','x^2','y^2','x*y'}},sampleRate,[0 1]./sampleRate); tc{3}.setName('Quadratic+Hist');
+tcc = ConfigColl(tc); makePlot=1; neuronNum=1;
+fitResults =Analysis.RunAnalysisForAllNeurons(trial,tcc,0);
+fitResults.plotResults;
+figure;
+[x_new,y_new]=meshgrid(-1:.1:1); %define new x and y
+y_new = flipud(y_new);
+x_new = fliplr(x_new);
+newData{1} =ones(size(x_new));
+newData{2} =x_new; newData{3} =y_new;
+newData{4} =x_new.^2; newData{5} =y_new.^2;
+newData{6} =x_new.*y_new;
+color = Analysis.colors;
+for i=1:fitResults.numResults
+lambda = fitResults.evalLambda(i,newData);
+h_mesh = mesh(x_new,y_new,lambda,'AlphaData',0);
+get(h_mesh,'AlphaData');
+set(h_mesh,'FaceAlpha',0.2,'EdgeAlpha',0.8,'EdgeColor',color{i});
+hold on;
+end
+legend(fitResults.lambda.dataLabels);
+plot(position.getSubSignal('x').dataToMatrix,position.getSubSignal('y').dataToMatrix,...
+values_at_spiketimes(:,1),values_at_spiketimes(:,2),'r.');
+axis tight square;
+xlabel('x position (m)'); ylabel('y position (m)');
+[b,dev,stats] = glmfit([xN yN xN.^2 yN.^2 xN.*yN],spikes_binned,'poisson');
+b-fitResults.b{2} % should be close to zero
+sampleRate=1000; makePlot=1; neuronNum = 1;
+covLabels = {{'Baseline','mu'},{'Radial','x','y','x^2','y^2','x*y'}};
+Algorithm = 'GLM';
+batchMode=0;
+windowTimes =(0:1:10)./sampleRate;
+[fitResults,tcc] = Analysis.computeHistLag(trial,neuronNum,windowTimes,covLabels,Algorithm,batchMode,sampleRate,makePlot);
diff --git a/parity/line_port_snapshots/ExplicitStimulusWhiskerData.txt b/parity/line_port_snapshots/ExplicitStimulusWhiskerData.txt
new file mode 100644
index 00000000..57300289
--- /dev/null
+++ b/parity/line_port_snapshots/ExplicitStimulusWhiskerData.txt
@@ -0,0 +1,115 @@
+close all;
+[~,~,explicitStimulusDir] = getPaperDataDirs();
+Direction=3; Neuron=1; Stim=2;
+datapath = fullfile(explicitStimulusDir,strcat('Dir', num2str(Direction)),...
+strcat('Neuron', num2str(Neuron)), strcat('Stim', num2str(Stim)));
+data=load(fullfile(datapath,'trngdataBis.mat'));
+time=0:.001:(length(data.t)-1)*.001;
+stimData = data.t;
+spikeTimes = time(data.y==1);
+stim = Covariate(time,stimData,'Stimulus','time','s','V',{'stim'});
+baseline = Covariate(time,ones(length(time),1),'Baseline','time','s','',...
+{'constant'});
+nst = nspikeTrain(spikeTimes);
+nspikeColl = nstColl(nst);
+cc = CovColl({stim,baseline});
+trial = Trial(nspikeColl,cc);
+trial.plot;
+figure;
+subplot(2,1,1);
+nst2 = nspikeTrain(spikeTimes);
+nst2.setMaxTime(21);nst.plot;
+subplot(2,1,2);
+stim.getSigInTimeWindow(0,21).plot;
+clear c;
+selfHist = [] ; NeighborHist = []; sampleRate = 1000;
+c{1} = TrialConfig({{'Baseline','constant'}},sampleRate,selfHist,NeighborHist);
+c{1}.setName('Baseline');
+cfgColl= ConfigColl(c);
+results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);
+figure;
+results.Residual.xcov(stim).windowedSignal([0,1]).plot;
+[m,ind,ShiftTime] = max(results.Residual.xcov(stim).windowedSignal([0,1]));
+stim = Covariate(time,stimData,'Stimulus','time','s','V',{'stim'});
+stim = stim.shift(ShiftTime);
+baseline = Covariate(time,ones(length(time),1),'Baseline','time','s','',...
+{'constant'});
+nst = nspikeTrain(spikeTimes);
+nspikeColl = nstColl(nst);
+cc = CovColl({stim,baseline});
+trial = Trial(nspikeColl,cc);
+clear c;
+selfHist = [] ; NeighborHist = []; sampleRate = 1000;
+c{1} = TrialConfig({{'Baseline','constant'}},sampleRate,selfHist,...
+NeighborHist);
+c{1}.setName('Baseline');
+c{2} = TrialConfig({{'Baseline','constant'},{'Stimulus','stim'}},...
+sampleRate,selfHist,NeighborHist);
+c{2}.setName('Baseline+Stimulus');
+cfgColl= ConfigColl(c);
+results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);
+results.plotResults;
+sampleRate=1000;
+delta=1/sampleRate*1;
+maxWindow=1; numWindows=30;
+windowTimes =unique(round([0 logspace(log10(delta),...
+log10(maxWindow),numWindows)]*sampleRate)./sampleRate);
+results =Analysis.computeHistLagForAll(trial,windowTimes,...
+{{'Baseline','constant'},{'Stimulus','stim'}},'BNLRCG',0,sampleRate,0);
+KSind = find(results{1}.KSStats.ks_stat == min(results{1}.KSStats.ks_stat));
+AICind = find((results{1}.AIC(2:end)-results{1}.AIC(1))== ...
+min(results{1}.AIC(2:end)-results{1}.AIC(1)));
+BICind = find((results{1}.BIC(2:end)-results{1}.BIC(1))== ...
+min(results{1}.BIC(2:end)-results{1}.BIC(1)));
+if(AICind==1)
+AICind=inf;
+end
+if(BICind==1)
+BICind=inf; %sometime BIC is non-decreasing and the index would be 1
+end
+windowIndex = min([KSind,AICind,BICind]) %use the minimum order model
+Summary = FitResSummary(results);
+Summary.plotSummary;
+clear c;
+if(windowIndex>1)
+selfHist = windowTimes(1:windowIndex);
+else
+selfHist = [];
+end
+NeighborHist = []; sampleRate = 1000;
+figure;
+x=1:length(windowTimes);
+subplot(3,1,1); plot(x,results{1}.KSStats.ks_stat,'.'); axis tight; hold on;
+plot(x(windowIndex),results{1}.KSStats.ks_stat(windowIndex),'r*');
+set(gca,'xtick',[]);
+ylabel('KS Statistic');
+dAIC = results{1}.AIC-results{1}.AIC(1);
+subplot(3,1,2); plot(x,dAIC,'.');
+set(gca,'xtick',[]);
+ylabel('\Delta AIC');axis tight; hold on;
+plot(x(windowIndex),dAIC(windowIndex),'r*');
+dBIC = results{1}.BIC-results{1}.BIC(1);
+subplot(3,1,3); plot(x,dBIC,'.');
+ylabel('\Delta BIC'); axis tight; hold on;
+plot(x(windowIndex),dBIC(windowIndex),'r*');
+for i=2:length(x)
+histLabels{i} = ['[' num2str(windowTimes(i-1),3) ',' num2str(windowTimes(i),3) ,']'];
+end
+figure;
+plot(x,dBIC,'.');
+xticks = 1:(length(histLabels));
+set(gca,'xtick',xticks,'xtickLabel',histLabels,'FontSize',6);
+if(max(xticks)>=1)
+xticklabel_rotate([],90,[],'Fontsize',8);
+end
+c{1} = TrialConfig({{'Baseline','constant'}},sampleRate,[],NeighborHist);
+c{1}.setName('Baseline');
+c{2} = TrialConfig({{'Baseline','constant'},{'Stimulus','stim'}},...
+sampleRate,[],[]);
+c{2}.setName('Baseline+Stimulus');
+c{3} = TrialConfig({{'Baseline','constant'},{'Stimulus','stim'}},...
+sampleRate,windowTimes(1:windowIndex),[]);
+c{3}.setName('Baseline+Stimulus+Hist');
+cfgColl= ConfigColl(c);
+results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);
+results.plotResults;
diff --git a/parity/line_port_snapshots/HistoryExamples.txt b/parity/line_port_snapshots/HistoryExamples.txt
new file mode 100644
index 00000000..ada6cd82
--- /dev/null
+++ b/parity/line_port_snapshots/HistoryExamples.txt
@@ -0,0 +1,18 @@
+spikeTimes = sort(rand(1,100))*1;
+nst = nspikeTrain(spikeTimes,'n1',.001);
+windowTimes = [.001 .002 .004];
+h=History(windowTimes);
+histn1=h.computeHistory(nst);
+figure; subplot(3,1,1); h.plot; ylabel('History Windows');
+subplot(3,1,2); histn1.plot; ylabel('History Covariate for nst');
+figure; nst.plot; ylabel('Neural Spike Train');
+clear nst;
+for i=1:1
+spikeTimes = sort(rand(1,100))*1;
+nst{i}=nspikeTrain(spikeTimes,'',.001);
+end
+spikeColl=nstColl(nst);
+windowTimes = [.001 .002 .01];
+h=History(windowTimes);
+histColl = h.computeHistory(spikeColl);
+figure; histColl.plot;
diff --git a/parity/line_port_snapshots/ValidationDataSet.txt b/parity/line_port_snapshots/ValidationDataSet.txt
new file mode 100644
index 00000000..3b0f03f8
--- /dev/null
+++ b/parity/line_port_snapshots/ValidationDataSet.txt
@@ -0,0 +1,77 @@
+clear all;
+close all;
+p=0.01; % bernoilli probability
+N=100001; % Number of coin flips
+delta = 0.001; % binsize
+T=N*delta; % total time window
+lambda=N*p/T % lambda*T = N*p
+mu = log(lambda*delta/(1-lambda*delta))
+for i=1:2
+t=linspace(0,T,N);
+ind=rand(1,N)T1);
+ind2=rand(1,N2)max(t1)],'Baseline','s','','',{'muConst','mu1','mu2'});
+cc=CovColl({cov});
+sampleRate=1000;
+trial=Trial(spikeColl, cc);
+clear c;
+c{1} = TrialConfig({{'Baseline','muConst'}},sampleRate,[],[]);
+c{1}.setName('Baseline');
+c{2} = TrialConfig({{'Baseline','mu1','mu2'}},sampleRate,[],[]);
+c{2}.setName('Variable');
+cfgColl= ConfigColl(c);
+results = Analysis.RunAnalysisForAllNeurons(trial,cfgColl,0);
+results{1}.plotResults;
+results{2}.plotResults;
+figure;
+subplot(1,2,1); results{1}.lambda.plot;
+subplot(1,2,2); results{2}.lambda.plot;
+Summary = FitResSummary(results);
+Summary.plotSummary;
diff --git a/parity/line_port_snapshots/mEPSCAnalysis.txt b/parity/line_port_snapshots/mEPSCAnalysis.txt
new file mode 100644
index 00000000..5dc8cc5e
--- /dev/null
+++ b/parity/line_port_snapshots/mEPSCAnalysis.txt
@@ -0,0 +1,48 @@
+close all;
+[~,mEPSCDir] = getPaperDataDirs();
+epsc2 = importdata(fullfile(mEPSCDir,'epsc2.txt'));
+sampleRate = 1000;
+spikeTimes = epsc2.data(:,2)*1/sampleRate; %in seconds
+nst = nspikeTrain(spikeTimes);
+time = 0:(1/sampleRate):nst.maxTime;
+baseline = Covariate(time,ones(length(time),1),'Baseline','time','s','',{'\mu'});
+covarColl = CovColl({baseline});
+spikeColl = nstColl(nst);
+trial = Trial(spikeColl,covarColl);
+clear tc tcc;
+tc{1} = TrialConfig({{'Baseline','\mu'}},sampleRate,[]); tc{1}.setName('Constant Baseline');
+tcc = ConfigColl(tc);
+results =Analysis.RunAnalysisForAllNeurons(trial,tcc,0);
+results.plotResults;
+washout1 = importdata(fullfile(mEPSCDir,'washout1.txt'));
+washout2 = importdata(fullfile(mEPSCDir,'washout2.txt'));
+sampleRate = 1000;
+spikeTimes1 = 260+washout1.data(:,2)*1/sampleRate; %in seconds
+spikeTimes2 = sort(washout2.data(:,2))*1/sampleRate + 745;%in seconds
+nst = nspikeTrain([spikeTimes1; spikeTimes2]);
+time = 260:(1/sampleRate):nst.maxTime;
+figure;
+nst.plot;
+timeInd1 =find(time<495,1,'last'); %0-495sec first constant rate
+timeInd2 =find(time<765,1,'last'); %495-765 second constant rate epoch
+constantRate = ones(length(time),1);
+rate1 = zeros(length(time),1); rate1(1:timeInd1)=1;
+rate2 = zeros(length(time),1); rate2((timeInd1+1):timeInd2)=1;
+rate3 = zeros(length(time),1); rate3((timeInd2+1):end)=1;
+baseline = Covariate(time,[constantRate,rate1, rate2, rate3],'Baseline','time','s','',{'\mu','\mu_{1}','\mu_{2}','\mu_{3}'});
+covarColl = CovColl({baseline});
+spikeColl = nstColl(nst);
+trial = Trial(spikeColl,covarColl);
+maxWindow=.3; numWindows=20;
+delta=1/sampleRate;
+windowTimes =unique(round([0 logspace(log10(delta),...
+log10(maxWindow),numWindows)]*sampleRate)./sampleRate);
+windowTimes = windowTimes(1:11);
+clear tc tcc;
+tc{1} = TrialConfig({{'Baseline','\mu'}},sampleRate,[]); tc{1}.setName('Constant Baseline');
+tc{2} = TrialConfig({{'Baseline','\mu_{1}','\mu_{2}','\mu_{3}'}},sampleRate,[]); tc{2}.setName('Diff Baseline');
+tcc = ConfigColl(tc);
+results =Analysis.RunAnalysisForAllNeurons(trial,tcc,0);
+results.plotResults;
+Summary = FitResSummary(results);
+Summary.plotSummary;
diff --git a/parity/strict_line_gap_sprint.md b/parity/strict_line_gap_sprint.md
new file mode 100644
index 00000000..8ea4d421
--- /dev/null
+++ b/parity/strict_line_gap_sprint.md
@@ -0,0 +1,35 @@
+# Strict Line-Port Gap Sprint
+
+- Source report: `/tmp/nstat_python_exec_next/parity/function_example_alignment_report.json`
+- Total topics: `30`
+- Strict summary: verified=1, partial=6, gap=19
+
+## Priority Queue
+| Priority | Topic | Coverage | Function recall | Code-line ratio (Py/MATLAB) | MATLAB lines | Python lines |
+|---:|---|---:|---:|---:|---:|---:|
+| 1 | SignalObjExamples | 0.0000 | 0.0833 | 0.5432 | 81 | 44 |
+| 2 | NetworkTutorial | 0.0000 | 0.1081 | 1.2500 | 88 | 110 |
+| 3 | DecodingExampleWithHist | 0.0000 | 0.1429 | 1.2545 | 55 | 69 |
+| 4 | StimulusDecode2D | 0.0000 | 0.1489 | 0.6848 | 92 | 63 |
+| 5 | AnalysisExamples | 0.0000 | 0.1795 | 1.0678 | 59 | 63 |
+| 6 | DecodingExample | 0.0000 | 0.1842 | 1.2105 | 57 | 69 |
+| 7 | PSTHEstimation | 0.0000 | 0.2143 | 1.6071 | 28 | 45 |
+| 8 | HybridFilterExample | 0.0069 | 0.1324 | 0.4896 | 288 | 141 |
+| 9 | PPSimExample | 0.0488 | 0.1111 | 1.8293 | 41 | 75 |
+| 10 | PPThinning | 0.0750 | 0.3000 | 2.3500 | 40 | 94 |
+| 11 | EventsExamples | 0.1250 | 0.2500 | 3.8750 | 8 | 31 |
+| 12 | CovariateExamples | 0.1579 | 0.7143 | 2.9474 | 19 | 56 |
+| 13 | TrialExamples | 0.1600 | 0.9091 | 3.1200 | 25 | 78 |
+| 14 | nSpikeTrainExamples | 0.3000 | 0.8333 | 4.2000 | 10 | 42 |
+| 15 | nstCollExamples | 0.3125 | 0.6364 | 3.5000 | 16 | 56 |
+| 16 | ConfigCollExamples | 0.3333 | 1.0000 | 11.0000 | 3 | 33 |
+| 17 | TrialConfigExamples | 0.3333 | 1.0000 | 9.3333 | 3 | 28 |
+| 18 | CovCollExamples | 0.7000 | 1.0000 | 5.6000 | 10 | 56 |
+| 19 | HistoryExamples | 1.0000 | 1.0000 | 4.0556 | 18 | 73 |
+
+## Execution Checklist
+- Export executable-line snapshots for each gap topic.
+- Regenerate notebooks with snapshot anchors.
+- Re-run `tools/parity/sync_parity_artifacts.py`.
+- Target strict status: `line_port_partial` or `line_port_verified`.
+
diff --git a/tools/parity/build_strict_line_gap_sprint.py b/tools/parity/build_strict_line_gap_sprint.py
new file mode 100644
index 00000000..e3fb3735
--- /dev/null
+++ b/tools/parity/build_strict_line_gap_sprint.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+"""Build a strict-line parity sprint backlog from equivalence audit output."""
+
+from __future__ import annotations
+
+import argparse
+import json
+from pathlib import Path
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ "--report",
+ type=Path,
+ default=Path("parity/function_example_alignment_report.json"),
+ help="Equivalence audit report JSON path.",
+ )
+ parser.add_argument(
+ "--output",
+ type=Path,
+ default=Path("parity/strict_line_gap_sprint.md"),
+ help="Output markdown backlog path.",
+ )
+ parser.add_argument(
+ "--top-n",
+ type=int,
+ default=20,
+ help="Number of strict line-gap topics to include.",
+ )
+ return parser.parse_args()
+
+
+def _f(value: float | int | None) -> str:
+ if value is None:
+ return "-"
+ return f"{float(value):.4f}"
+
+
+def main() -> int:
+ args = parse_args()
+ payload = json.loads(args.report.read_text(encoding="utf-8"))
+ audit = payload.get("example_line_alignment_audit", {})
+ summary = audit.get("summary", {})
+ rows = list(audit.get("topic_rows", []))
+
+ gaps = [row for row in rows if row.get("strict_line_status") == "line_port_gap"]
+ gaps.sort(
+ key=lambda row: (
+ float(row.get("line_port_coverage", 0.0)),
+ float(row.get("line_port_function_recall", 0.0)),
+ -float(row.get("matlab_code_lines", 0.0)),
+ )
+ )
+ selected = gaps[: max(0, args.top_n)]
+
+ lines: list[str] = []
+ lines.append("# Strict Line-Port Gap Sprint")
+ lines.append("")
+ lines.append(f"- Source report: `{args.report}`")
+ lines.append(f"- Total topics: `{summary.get('total_topics', 0)}`")
+ lines.append(
+ "- Strict summary: "
+ f"verified={summary.get('strict_line_verified_topics', 0)}, "
+ f"partial={summary.get('strict_line_partial_topics', 0)}, "
+ f"gap={summary.get('strict_line_gap_topics', len(gaps))}"
+ )
+ lines.append("")
+ lines.append("## Priority Queue")
+ lines.append(
+ "| Priority | Topic | Coverage | Function recall | Code-line ratio (Py/MATLAB) | MATLAB lines | Python lines |"
+ )
+ lines.append("|---:|---|---:|---:|---:|---:|---:|")
+ for i, row in enumerate(selected, start=1):
+ lines.append(
+ "| "
+ f"{i} | {row.get('topic', '-')}"
+ f" | {_f(row.get('line_port_coverage'))}"
+ f" | {_f(row.get('line_port_function_recall'))}"
+ f" | {_f(row.get('python_to_matlab_line_ratio'))}"
+ f" | {int(row.get('matlab_code_lines', 0))}"
+ f" | {int(row.get('python_code_lines', 0))} |"
+ )
+ lines.append("")
+ lines.append("## Execution Checklist")
+ lines.append("- Export executable-line snapshots for each gap topic.")
+ lines.append("- Regenerate notebooks with snapshot anchors.")
+ lines.append("- Re-run `tools/parity/sync_parity_artifacts.py`.")
+ lines.append("- Target strict status: `line_port_partial` or `line_port_verified`.")
+ lines.append("")
+
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text("\n".join(lines) + "\n", encoding="utf-8")
+ print(f"Wrote strict-line sprint backlog: {args.output}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())