Expand NTT course with visual CT/GS notebooks

This commit is contained in:
saymrwulf 2026-04-15 21:12:40 +02:00
parent 08e864ac12
commit 901d465e38
34 changed files with 6902 additions and 683 deletions

View file

@ -7,22 +7,10 @@
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Why This Order Exists"
"title": "What This Course Separates"
}
},
"source": "## META | difficulty 1 | Why This Order Exists\n\nThe course separates three ideas that are often blurred together:\n\n- the algebraic purpose of the NTT\n- the in-place butterfly dataflow\n- Kyber-specific implementation conventions\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "route",
"title": "Learning Staircase"
}
},
"source": "## MANDATORY | difficulty 2 | Learning Staircase\n\nThe supported staircase is:\n\n1. ordinary polynomial multiplication and convolution\n2. negacyclic multiplication\n3. a tiny toy NTT\n4. butterfly mechanics in isolation\n5. forward and inverse flow side by side\n6. Kyber-specific parameters and indexing\n7. real implementation patterns\n"
"source": "## META | difficulty 1 | What This Course Separates\n\nThis course keeps three stories separate on purpose:\n\n- the algebraic purpose of the transform\n- the local in-place butterfly dataflow\n- the Kyber-specific implementation conventions\n\nThe point is to stop those three from collapsing into one blurry \u201cFFT-like thing\u201d.\n"
},
{
"cell_type": "markdown",
@ -31,22 +19,34 @@
"role": "mandatory",
"difficulty": 2,
"kind": "structure",
"title": "Bundle Rhythm"
"title": "The Learning Staircase"
}
},
"source": "## MANDATORY | difficulty 2 | Bundle Rhythm\n\nTechnical bundles follow a consistent rhythm:\n\n- `lecture.ipynb` explains the idea carefully\n- `lab.ipynb` asks for predictions before execution\n- `problems.ipynb` checks retrieval and reflection\n- `studio.ipynb` compares implementation choices and debugging cues\n"
"source": "## MANDATORY | difficulty 2 | The Learning Staircase\n\nThe supported staircase is:\n\n1. schoolbook multiplication and diagonals\n2. cyclic and negacyclic wraparound\n3. direct negative-wrapped NTT and iNTT\n4. fast forward CT butterflies\n5. fast inverse GS butterflies\n6. bit-reversal and ordering\n7. Kyber parameter reality and base multiplication\n8. debugging wrong sign / wrong zeta / wrong order / wrong scale failures\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "constraints",
"title": "Route Constraints"
"role": "mandatory",
"difficulty": 2,
"kind": "bundles",
"title": "Bundles"
}
},
"source": "## META | difficulty 1 | Route Constraints\n\nRoute notebooks stay pure route notebooks.\n\n- no facultative detours here\n- no hidden competing learner route\n- every notebook ends with a visible handoff\n"
"source": "## MANDATORY | difficulty 2 | Bundles\n\nEach serious module uses the same rhythm:\n\n- `lecture.ipynb` = slow explanation plus visual demos\n- `lab.ipynb` = prediction before execution\n- `problems.ipynb` = retrieval and reflection\n- `studio.ipynb` = comparison, debugging, and implementation reading\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "expectation",
"title": "What \u201cBlunt And Graphical\u201d Means Here"
}
},
"source": "## MANDATORY | difficulty 2 | What \u201cBlunt And Graphical\u201d Means Here\n\nThe notebooks should not ask the learner to imagine too much in their head.\n\nExpect:\n\n- schoolbook product grids\n- wraparound arrows\n- explicit stage arrays\n- stage sliders\n- bit-reversal wire maps\n- side-by-side wrong vs right traces\n"
},
{
"cell_type": "markdown",
@ -72,14 +72,35 @@
},
"ntt_learning": {
"title": "Course Blueprint",
"contract_version": "0.1",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb"
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},

View file

@ -0,0 +1,85 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Route Complete"
}
},
"source": "## META | difficulty 1 | Route Complete\n\nYou reached the end of the supported route.\n\nIf the course did its job, you should now be able to separate:\n\n- raw polynomial multiplication\n- negacyclic structure\n- direct NTT / iNTT definitions\n- fast CT / GS butterfly flow\n- order changes and scaling\n- Kyber\u2019s specific modulus and base-multiplication story\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Exit Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Exit Reflection\n\nFinal written prompts:\n\n1. Explain the difference between \u201cthe transform as mathematics\u201d and \u201cthe butterfly network as an implementation strategy\u201d.\n2. Explain why Kyber v3 needs more than the naive \u201cjust use a 2n-th root\u201d mental model.\n3. Name the first four checks you would run if an iNTT output looked wrong.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: you are at the end of the supported route. Revisit the studios if you want more repetition.\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Course Complete",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -10,7 +10,7 @@
"title": "Welcome"
}
},
"source": "## META | difficulty 1 | Welcome\n\nThis course is local-first and notebook-first. Every visible cell is labeled so the learner always knows\nwhether a cell is route guidance, required walkthrough material, or an optional detour.\n\nContract:\n\n- `META` = route, pacing, and handoff guidance\n- `MANDATORY` = the official walkthrough\n- `FACULTATIVE` = optional extension\n"
"source": "## META | difficulty 1 | Welcome\n\nThis course is for people who need to see the algorithm move.\n\nThe goal is not to hide behind abstract formulas. The goal is to make the NTT and iNTT feel physically inspectable:\n\n- every stage should look like values moving through wires\n- every wraparound should be visible\n- every ordering change should be concrete\n- every Kyber-specific choice should be motivated by what the arithmetic allows\n"
},
{
"cell_type": "markdown",
@ -22,7 +22,19 @@
"title": "Official Route"
}
},
"source": "## MANDATORY | difficulty 1 | Official Route\n\nFollow exactly one supported path:\n\n1. `START_HERE.ipynb`\n2. `COURSE_BLUEPRINT.ipynb`\n3. `notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb`\n4. `notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb`\n5. `notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb`\n6. `notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb`\n\nThe course starts with concrete arrays and small examples before any Kyber-specific implementation details.\n"
"source": "## MANDATORY | difficulty 1 | Official Route\n\nFollow exactly one supported route:\n\n1. `START_HERE.ipynb`\n2. `COURSE_BLUEPRINT.ipynb`\n3. each bundle in `Lecture -> Lab -> Problems -> Studio` order\n4. `COURSE_COMPLETE.ipynb`\n\nSupported bundles:\n\n- `foundations/01_convolution_to_toy_ntt`\n- `foundations/02_negative_wrapped_ntt`\n- `butterfly_mechanics/03_fast_forward_ct`\n- `butterfly_mechanics/04_fast_inverse_gs`\n- `kyber_mapping/05_kyber_ntt_and_base_multiplication`\n- `professional/06_debugging_ntt_failures`\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 1,
"kind": "contract",
"title": "Visible Cell Contract"
}
},
"source": "## MANDATORY | difficulty 1 | Visible Cell Contract\n\nCell labels are not decoration. They tell you how to use the notebook:\n\n- `META` = route, pacing, and handoff\n- `MANDATORY` = the official walkthrough\n- `FACULTATIVE` = optional deepening only\n- difficulty `1-3` is reserved for mandatory work\n- difficulty `4-10` is reserved for facultative work\n"
},
{
"cell_type": "markdown",
@ -34,7 +46,7 @@
"title": "Local Operations"
}
},
"source": "## META | difficulty 1 | Local Operations\n\nRepo-local commands:\n\n- `scripts/bootstrap.sh` creates `.venv` and installs dependencies\n- `scripts/validate.sh` runs structural and execution checks\n- `scripts/start.sh` launches JupyterLab when it is installed\n"
"source": "## META | difficulty 1 | Local Operations\n\nRepo-local commands:\n\n- `scripts/bootstrap.sh`\n- `scripts/start.sh`\n- `scripts/status.sh`\n- `scripts/validate.sh`\n"
},
{
"cell_type": "markdown",
@ -60,14 +72,35 @@
},
"ntt_learning": {
"title": "Start Here",
"contract_version": "0.1",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb"
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Lab Goals"
}
},
"source": "## META | difficulty 1 | Lab Goals\n\nYou should predict stage pairings and zetas before running the stage explorer.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 1"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 1\n\nFor the `n = 4` example, predict:\n\n- which original coefficients pair in stage 1\n- which adjacent positions pair in stage 2\n- why the output is not yet in normal order\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Prediction Check For n=4"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Prediction Check For n=4\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace\nfrom ntt_learning.visuals import interactive_trace\n\ntrace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\ndisplay(interactive_trace(trace, title=\"Check your n=4 prediction\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 2"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 2\n\nFor the `n = 8` example, name the stage count before you run the next cell.\nThen name which stage feels most confusing and why.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Prediction Check For n=8"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Prediction Check For n=8\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, find_psi\nfrom ntt_learning.visuals import interactive_trace\n\ntrace = fast_ntt_psi_ct_trace([0, 1, 2, 3, 4, 5, 6, 7], 97, find_psi(8, 97))\ndisplay(interactive_trace(trace, title=\"Check your n=8 prediction\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- Which stage feels easiest to see by eye?\n- Which stage feels most like \u201cpure schedule\u201d rather than \u201cnew algebra\u201d?\n- Why is BO output not a bug?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Change The n=8 Signal"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Change The n=8 Signal\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, find_psi\nfrom ntt_learning.visuals import interactive_trace\n\npsi = find_psi(8, 97)\n\ndef preview(a0=0, a1=1, a2=2, a3=3, a4=4, a5=5, a6=6, a7=7):\n trace = fast_ntt_psi_ct_trace([a0, a1, a2, a3, a4, a5, a6, a7], 97, psi)\n display(interactive_trace(trace, title=\"Interactive n=8 CT trace\"))\n\ndisplay(\n widgets.interact(\n preview,\n a0=(0, 12),\n a1=(0, 12),\n a2=(0, 12),\n a3=(0, 12),\n a4=(0, 12),\n a5=(0, 12),\n a6=(0, 12),\n a7=(0, 12),\n )\n)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `problems.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lab: Fast Forward CT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,165 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Objectives"
}
},
"source": "## META | difficulty 1 | Objectives\n\nThis bundle is where the transform stops being a full matrix multiplication and becomes a staged butterfly network.\n\nFocus:\n\n- CT as the fast forward NTT strategy\n- visible stage arrays\n- explicit zeta values per pair\n- BO output vs NO output\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "CT Is A Schedule For Reusing Work"
}
},
"source": "## MANDATORY | difficulty 3 | CT Is A Schedule For Reusing Work\n\nThe point of the CT butterfly is not to invent a new transform.\nThe point is to compute the same transform by reusing shared bracket terms instead of recomputing everything from scratch.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Trace The Exact n=4 Paper Example"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Trace The Exact n=4 Paper Example\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, forward_ntt_psi\nfrom ntt_learning.visuals import interactive_trace, plot_trace_overview\n\nsignal = [1, 2, 3, 4]\nmodulus = 7681\npsi = 1925\ntrace = fast_ntt_psi_ct_trace(signal, modulus, psi)\n\nprint(\"raw CT output (BO):\", trace.raw_output)\nprint(\"bit-reversed back to NO:\", trace.normal_order_output)\nprint(\"direct NTT_psi:\", forward_ntt_psi(signal, modulus, psi))\ndisplay(plot_trace_overview(trace, title=\"CT overview for [1,2,3,4]\"))\ndisplay(interactive_trace(trace, title=\"CT forward trace\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "What You Should Notice In The Stage Viewer"
}
},
"source": "## MANDATORY | difficulty 3 | What You Should Notice In The Stage Viewer\n\nDo not just read the final answer.\nNotice:\n\n- which pairs talk to each other in each stage\n- which `zeta` each pair uses\n- how the array order changes before the final bit-reversal correction\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Run The Second n=4 Paper Example"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Run The Second n=4 Paper Example\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace\nfrom ntt_learning.visuals import interactive_trace\n\nsignal = [5, 6, 7, 8]\ntrace = fast_ntt_psi_ct_trace(signal, 7681, 1925)\nprint(\"BO output:\", trace.raw_output)\nprint(\"NO output:\", trace.normal_order_output)\ndisplay(interactive_trace(trace, title=\"Second CT trace\"))\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Go One Stage Deeper With n=8"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Go One Stage Deeper With n=8\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, find_psi\nfrom ntt_learning.visuals import interactive_trace, plot_trace_overview\n\nsignal = [0, 1, 2, 3, 4, 5, 6, 7]\nmodulus = 97\npsi = find_psi(8, modulus)\ntrace = fast_ntt_psi_ct_trace(signal, modulus, psi)\n\nprint(\"psi:\", psi)\nprint(\"BO output:\", trace.raw_output)\nprint(\"NO output:\", trace.normal_order_output)\ndisplay(plot_trace_overview(trace, title=\"Three CT stages for n=8\"))\ndisplay(interactive_trace(trace, title=\"n=8 CT stage explorer\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Retrieval Check"
}
},
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\n1. What does CT change: the transform definition or the computation schedule?\n2. Why is the output naturally in BO rather than NO?\n3. In a stage diagram, what are the first three things you should inspect before any formula?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Inspect Stage Rows As Data"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Inspect Stage Rows As Data\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, stage_rows\n\ntrace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\nfor stage in trace.stages:\n print(\"stage\", stage.stage_index)\n for row in stage_rows(stage):\n print(row)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `lab.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lecture: Fast Forward CT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Problem Set Goals"
}
},
"source": "## META | difficulty 1 | Problem Set Goals\n\nThis notebook checks whether the CT schedule is now a visible object rather than a name.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Multiple-Choice Retrieval"
}
},
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. CT makes the transform faster by:\n A. changing the ring\n B. reusing bracket structure through staged butterflies\n C. deleting the inverse\n\n2. The natural output order of the direct CT schedule discussed here is:\n A. normal order\n B. bit-reversed order\n C. random order\n\n3. A stage explorer is useful mainly because it:\n A. hides the pairings\n B. makes data movement and zeta usage inspectable\n C. removes the need for examples\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Answer Key"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {1: \"B\", 2: \"B\", 3: \"B\"}\nprint(answers)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Paper Example Check"
}
},
"source": "## MANDATORY | difficulty 2 | Paper Example Check\n\nVerify that the CT trace on `[1,2,3,4]` in `Z_7681` with `\u03c8 = 1925` lands on the paper\u2019s BO output.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Check The BO Output"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Check The BO Output\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace\n\ntrace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\nprint(trace.raw_output)\nassert list(trace.raw_output) == [1467, 3471, 2807, 7621]\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Written Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nExplain why the phrase \u201csame transform, better schedule\u201d is the right headline for CT.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Challenge"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional Challenge\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, find_psi\n\ntrace = fast_ntt_psi_ct_trace([3, 1, 4, 1, 5, 9, 2, 6], 97, find_psi(8, 97))\nfor stage in trace.stages:\n print(stage.stage_index, stage.output_values)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `studio.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Problems: Fast Forward CT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Studio Goals"
}
},
"source": "## META | difficulty 1 | Studio Goals\n\nThis studio compares CT traces and inspects where learners usually lose the plot: stage order, pair order, and BO output.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Same Schedule, Different Signals"
}
},
"source": "## MANDATORY | difficulty 3 | Same Schedule, Different Signals\n\nThe butterfly pattern is structural.\nThe signal values change, but the pairing pattern and the zeta schedule stay tied to `n`, the modulus, and the chosen root.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Compare Two CT Traces"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Compare Two CT Traces\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace\nfrom ntt_learning.visuals import plot_trace_overview\n\ntrace_a = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\ntrace_b = fast_ntt_psi_ct_trace([5, 6, 7, 8], 7681, 1925)\n\ndisplay(plot_trace_overview(trace_a, title=\"CT trace A\"))\ndisplay(plot_trace_overview(trace_b, title=\"CT trace B\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Debug Checklist"
}
},
"source": "## MANDATORY | difficulty 2 | Debug Checklist\n\nIf a CT implementation looks wrong, inspect:\n\n1. the chosen `\u03c8`\n2. the stage pairings\n3. the zeta exponent sequence\n4. whether you remembered the final BO -> NO reorder when comparing against the direct transform\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "See A Wrong-Order Comparison Failure"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | See A Wrong-Order Comparison Failure\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, forward_ntt_psi\n\ntrace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\ndirect = forward_ntt_psi([1, 2, 3, 4], 7681, 1925)\n\nprint(\"wrong comparison: CT BO output vs direct NO output\")\nprint(trace.raw_output, direct)\nprint(\"correct comparison: CT NO output vs direct NO output\")\nprint(trace.normal_order_output, direct)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nIn one paragraph, explain why a learner can understand the direct transform and still get lost in CT if the stage order and output order are not shown visually.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Inspect The zeta schedule"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Inspect The zeta schedule\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace\n\ntrace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\nfor stage in trace.stages:\n print(\"stage\", stage.stage_index, \"zetas:\", stage.zetas)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `../../../butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Studio: Fast Forward CT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Lab Goals"
}
},
"source": "## META | difficulty 1 | Lab Goals\n\nPredict the BO input and the final scaling before you run the stage explorer.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 1"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 1\n\nExplain before running the next cell:\n\n- why the GS input must be BO in the standard schedule\n- why the unscaled output is not yet the original signal\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Prediction Check"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Prediction Check\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\nfrom ntt_learning.visuals import interactive_trace\n\ntrace = fast_intt_psi_gs_trace([1467, 3471, 2807, 7621], 7681, 1925)\ndisplay(interactive_trace(trace, title=\"Check your GS prediction\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 2"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 2\n\nPredict the bit-reversal of `[0,1,2,3,4,5,6,7]` before running the next cell.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Prediction Check For Ordering"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Prediction Check For Ordering\n\nfrom ntt_learning.toy_ntt import bit_reversed_order\n\nprint(bit_reversed_order([0, 1, 2, 3, 4, 5, 6, 7]))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- Which part of GS felt like a true inverse to you?\n- Which part felt like pure bookkeeping?\n- Why is the ordering story impossible to ignore?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Explore Another BO Input"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Explore Another BO Input\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\nfrom ntt_learning.visuals import interactive_trace\n\ndef preview(x0=1467, x1=3471, x2=2807, x3=7621):\n trace = fast_intt_psi_gs_trace([x0, x1, x2, x3], 7681, 1925)\n display(interactive_trace(trace, title=\"Interactive GS trace\"))\n\ndisplay(\n widgets.interact(\n preview,\n x0=(0, 7680),\n x1=(0, 7680),\n x2=(0, 7680),\n x3=(0, 7680),\n )\n)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `problems.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lab: Fast Inverse GS",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,165 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Objectives"
}
},
"source": "## META | difficulty 1 | Objectives\n\nThis bundle makes the inverse flow explicit.\n\nFocus:\n\n- GS as the fast inverse schedule\n- BO input and NO output\n- why the final `n^-1` scaling appears\n- how bit-reversal fits the forward/inverse pair\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "GS Feels Like The Same Network Seen From The Other End"
}
},
"source": "## MANDATORY | difficulty 3 | GS Feels Like The Same Network Seen From The Other End\n\nThe inverse is not \u201cmysterious undoing\u201d.\nIt is a staged network with the same family resemblance as CT, but with a different direction of arithmetic and a final scaling.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Trace The Exact n=4 GS Paper Example"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Trace The Exact n=4 GS Paper Example\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\nfrom ntt_learning.visuals import interactive_trace, plot_trace_overview\n\nbo_input = [1467, 3471, 2807, 7621]\ntrace = fast_intt_psi_gs_trace(bo_input, 7681, 1925)\n\nprint(\"unscaled NO output:\", trace.raw_output)\nprint(\"scaled NO output:\", trace.scaled_output)\ndisplay(plot_trace_overview(trace, title=\"GS overview for the n=4 paper example\"))\ndisplay(interactive_trace(trace, title=\"GS inverse trace\"))\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "See The Bit-Reversal Map Explicitly"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | See The Bit-Reversal Map Explicitly\n\nfrom IPython.display import display\n\nfrom ntt_learning.visuals import plot_bit_reversal_mapping\n\ndisplay(plot_bit_reversal_mapping(4, title=\"Bit-reversal for n=4\"))\ndisplay(plot_bit_reversal_mapping(8, title=\"Bit-reversal for n=8\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Why Scaling Waits Until The End"
}
},
"source": "## MANDATORY | difficulty 3 | Why Scaling Waits Until The End\n\nEach GS stage avoids local division by `2`.\nThe accumulated effect of those missing local divisions is corrected by the final multiplication with `n^-1`.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Full Forward And Inverse Round Trip"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Full Forward And Inverse Round Trip\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace, fast_ntt_psi_ct_trace\n\nsignal = [1, 2, 3, 4]\nforward_trace = fast_ntt_psi_ct_trace(signal, 7681, 1925)\ninverse_trace = fast_intt_psi_gs_trace(forward_trace.raw_output, 7681, 1925)\n\nprint(\"forward BO output:\", forward_trace.raw_output)\nprint(\"inverse scaled output:\", inverse_trace.scaled_output)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Retrieval Check"
}
},
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\n1. Why does GS want BO input?\n2. Why does the final scaling not disappear?\n3. What would go wrong if you visually compared GS input and CT NO output without respecting the order change?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: A Second GS Example"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: A Second GS Example\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\nfrom ntt_learning.visuals import interactive_trace\n\ntrace = fast_intt_psi_gs_trace([2489, 6478, 7489, 6607], 7681, 1925)\nprint(\"scaled output:\", trace.scaled_output)\ndisplay(interactive_trace(trace, title=\"Second GS trace\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `lab.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lecture: Fast Inverse GS",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Problem Set Goals"
}
},
"source": "## META | difficulty 1 | Problem Set Goals\n\nThis notebook checks whether the inverse flow, ordering, and scaling are now mechanically stable.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Multiple-Choice Retrieval"
}
},
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. GS is used here as the fast schedule for:\n A. direct forward NTT\n B. inverse NTT\n C. schoolbook multiplication\n\n2. The final `n^-1` matters because:\n A. each stage skipped local divisions that accumulate\n B. the modulus changed mid-computation\n C. bit-reversal requires scaling\n\n3. In the standard pairing, GS expects:\n A. NO input and BO output\n B. BO input and NO output\n C. random order on both ends\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Answer Key"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {1: \"B\", 2: \"A\", 3: \"B\"}\nprint(answers)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Paper Example Check"
}
},
"source": "## MANDATORY | difficulty 2 | Paper Example Check\n\nVerify that the GS trace on the paper\u2019s BO input scales back to `[1,2,3,4]`.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Check The Final Scaling"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Check The Final Scaling\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\n\ntrace = fast_intt_psi_gs_trace([1467, 3471, 2807, 7621], 7681, 1925)\nprint(trace.scaled_output)\nassert list(trace.scaled_output) == [1, 2, 3, 4]\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Written Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nIn one paragraph, explain why \u201csame structure, opposite direction\u201d is a better intuition for GS than \u201ctotally different algorithm\u201d.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Challenge"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional Challenge\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\n\ntrace = fast_intt_psi_gs_trace([2489, 6478, 7489, 6607], 7681, 1925)\nfor stage in trace.stages:\n print(stage.stage_index, stage.output_values)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `studio.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Problems: Fast Inverse GS",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Studio Goals"
}
},
"source": "## META | difficulty 1 | Studio Goals\n\nThe studio puts CT and GS next to each other and treats ordering as a first-class object, not a side note.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Forward And Inverse Need To Meet In The Middle Cleanly"
}
},
"source": "## MANDATORY | difficulty 3 | Forward And Inverse Need To Meet In The Middle Cleanly\n\nThe whole point of the pair is:\n\n- CT gets you into the transform domain efficiently\n- GS gets you back out efficiently\n- the two only meet cleanly if you respect BO/NO and the final scaling\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "See CT Output Feed GS Input"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | See CT Output Feed GS Input\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace, fast_ntt_psi_ct_trace\n\nsignal = [5, 6, 7, 8]\nforward_trace = fast_ntt_psi_ct_trace(signal, 7681, 1925)\ninverse_trace = fast_intt_psi_gs_trace(forward_trace.raw_output, 7681, 1925)\n\nprint(\"CT BO output:\", forward_trace.raw_output)\nprint(\"GS scaled output:\", inverse_trace.scaled_output)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Debug Checklist"
}
},
"source": "## MANDATORY | difficulty 2 | Debug Checklist\n\nIf the inverse output is wrong, inspect:\n\n1. whether the input was BO\n2. whether the zetas were inverse-stage zetas\n3. whether the final `n^-1` scaling was applied\n4. whether you compared the correct order against the direct reference\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "See A Missing-Scale Failure"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | See A Missing-Scale Failure\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace\n\ntrace = fast_intt_psi_gs_trace([1467, 3471, 2807, 7621], 7681, 1925)\nprint(\"unscaled:\", trace.raw_output)\nprint(\"scaled:\", trace.scaled_output)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nExplain why \u201cthe inverse looked almost right\u201d is a dangerous debugging sentence unless you say what happened with ordering and scaling.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Another bit-reversal map"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Another bit-reversal map\n\nfrom IPython.display import display\n\nfrom ntt_learning.visuals import plot_bit_reversal_mapping\n\ndisplay(plot_bit_reversal_mapping(16, title=\"Bit-reversal for n=16\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `../../../kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Studio: Fast Inverse GS",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -10,7 +10,7 @@
"title": "Lab Goals"
}
},
"source": "## META | difficulty 1 | Lab Goals\n\nThis lab asks for prediction before execution.\n\nThe learner should pause and name the expected pairings and sign changes before reading the output.\n"
"source": "## META | difficulty 1 | Lab Goals\n\nPredict the movement before you run the code.\n\nThe point is not just to see the picture after the fact.\nThe point is to force your eye to anticipate where the products and wraparound terms will land.\n"
},
{
"cell_type": "markdown",
@ -22,7 +22,7 @@
"title": "Exercise 1"
}
},
"source": "## MANDATORY | difficulty 2 | Exercise 1\n\nBefore running the next cell, predict which coefficients will collide when the raw convolution is folded into `x^4 + 1`.\n"
"source": "## MANDATORY | difficulty 2 | Exercise 1\n\nBefore running the next cell:\n\n- name the diagonal sums of the raw schoolbook grid\n- say which tail terms will wrap into slot `0`\n- say whether they add or subtract in `x^4 + 1`\n"
},
{
"cell_type": "code",
@ -32,23 +32,23 @@
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Work Two Multiplication Examples"
"title": "Run The Prediction Check"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Work Two Multiplication Examples\n\nfrom ntt_learning.toy_ntt import negacyclic_multiply, schoolbook_convolution\n\nsamples = [\n ([1, 2, 0, 0], [3, 4, 0, 0]),\n ([5, 0, 1, 2], [2, 1, 0, 1]),\n]\n\nfor left, right in samples:\n print(\"left:\", left, \"right:\", right)\n print(\" convolution:\", schoolbook_convolution(left, right))\n print(\" negacyclic:\", negacyclic_multiply(left, right, n=4))\n"
"source": "# MANDATORY | difficulty 2 | Run The Prediction Check\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import negacyclic_multiply, schoolbook_convolution\nfrom ntt_learning.visuals import plot_convolution_grid, plot_wraparound\n\nleft = [3, 0, 2, 1]\nright = [1, 4, 0, 2]\nraw = schoolbook_convolution(left, right)\n\nprint(\"raw convolution:\", raw)\nprint(\"negacyclic result:\", negacyclic_multiply(left, right, n=4))\ndisplay(plot_convolution_grid(left, right, title=\"Prediction check grid\"))\ndisplay(plot_wraparound(raw, n=4, negacyclic=True, title=\"Prediction check fold\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"difficulty": 2,
"kind": "exercise",
"title": "Exercise 2"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 2\n\nPredict the pairings for a single Cooley-Tukey stage on eight values with block size four.\nName the index pairs before running the cell.\n"
"source": "## MANDATORY | difficulty 2 | Exercise 2\n\nPick one number in the raw tail and follow it all the way to its final slot.\nDo not say \u201cit wraps around\u201d.\nSay exactly:\n\n- where it started\n- how many wraps happened\n- whether the sign flipped\n- where it finished\n"
},
{
"cell_type": "code",
@ -56,13 +56,13 @@
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"difficulty": 2,
"kind": "exercise",
"title": "Trace One Butterfly Layer"
"title": "A Second Visual Drill"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Trace One Butterfly Layer\n\nfrom ntt_learning.toy_ntt import action_rows, apply_ct_stage\n\nvalues = [0, 1, 2, 3, 4, 5, 6, 7]\nstage_output, stage_actions = apply_ct_stage(\n values,\n block_size=4,\n zetas=[1, 4, 1, 4],\n modulus=17,\n)\n\nprint(\"stage output:\", stage_output)\nfor row in action_rows(stage_actions):\n print(row)\n"
"source": "# MANDATORY | difficulty 2 | A Second Visual Drill\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import schoolbook_convolution\nfrom ntt_learning.visuals import plot_wraparound\n\nraw = schoolbook_convolution([2, 5, 0, 1], [1, 0, 3, 2])\nprint(\"raw convolution:\", raw)\ndisplay(plot_wraparound(raw, n=4, negacyclic=True, title=\"Trace one tail coefficient by eye\"))\n"
},
{
"cell_type": "markdown",
@ -74,7 +74,7 @@
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- Which part of the stage felt mechanical and local?\n- Which part still feels global or mysterious?\n- If one zeta is wrong, what kind of output difference would you expect to see?\n"
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- What felt easier to see in the grid than in symbolic polynomial notation?\n- What exactly makes negacyclic folding more annoying than ordinary wraparound?\n- If you had to explain `x^n + 1` to somebody visually, what would you draw?\n"
},
{
"cell_type": "code",
@ -84,11 +84,11 @@
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Inverse-Style Stage"
"title": "Optional: Try Your Own Arrays"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional Inverse-Style Stage\n\nfrom ntt_learning.toy_ntt import action_rows, apply_gs_stage\n\nvalues = [5, 1, 9, 3, 7, 2, 6, 4]\nstage_output, stage_actions = apply_gs_stage(\n values,\n block_size=4,\n zetas=[1, 4, 1, 4],\n modulus=17,\n)\n\nprint(\"stage output:\", stage_output)\nfor row in action_rows(stage_actions):\n print(row)\n"
"source": "# FACULTATIVE | difficulty 4 | Optional: Try Your Own Arrays\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import schoolbook_convolution\nfrom ntt_learning.visuals import plot_convolution_grid, plot_wraparound\n\ndef preview(a0=1, a1=2, a2=3, a3=4, b0=5, b1=6, b2=7, b3=8):\n left = [a0, a1, a2, a3]\n right = [b0, b1, b2, b3]\n raw = schoolbook_convolution(left, right)\n display(plot_convolution_grid(left, right, title=\"Interactive schoolbook grid\"))\n display(plot_wraparound(raw, n=4, negacyclic=True, title=\"Interactive negacyclic fold\"))\n\ndisplay(\n widgets.interact(\n preview,\n a0=(0, 6),\n a1=(0, 6),\n a2=(0, 6),\n a3=(0, 6),\n b0=(0, 6),\n b1=(0, 6),\n b2=(0, 6),\n b3=(0, 6),\n )\n)\n"
},
{
"cell_type": "markdown",
@ -114,14 +114,35 @@
},
"ntt_learning": {
"title": "Lab: Convolution To Toy NTT",
"contract_version": "0.1",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb"
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},

View file

@ -10,7 +10,7 @@
"title": "Objectives"
}
},
"source": "## META | difficulty 1 | Objectives\n\nThis notebook introduces the first technical bundle.\n\nFocus:\n\n- why polynomial multiplication matters\n- what negacyclic folding changes\n- how a tiny toy NTT gives a matrix-level view\n- what a butterfly does locally\n"
"source": "## META | difficulty 1 | Objectives\n\nThis first bundle is about making the raw multiplication problem visible before any transform is introduced.\n\nFocus:\n\n- the schoolbook product grid\n- diagonal sums\n- cyclic vs negacyclic folding\n- a tiny teaser of why transforms help\n"
},
{
"cell_type": "markdown",
@ -19,10 +19,10 @@
"role": "mandatory",
"difficulty": 2,
"kind": "explanation",
"title": "Convolution Before Transforms"
"title": "Schoolbook Multiplication Is A Grid"
}
},
"source": "## MANDATORY | difficulty 2 | Convolution Before Transforms\n\nStart with the concrete problem. Two coefficient arrays multiply by accumulating all pairwise products.\nThat schoolbook view is the baseline the learner should be able to inspect by hand before a transform is introduced.\n"
"source": "## MANDATORY | difficulty 2 | Schoolbook Multiplication Is A Grid\n\nStop thinking \u201cmultiply two polynomials\u201d as one sentence.\nThe mechanical reality is a grid of pairwise products whose diagonals have to be accumulated.\n\nIf that grid is not concrete, the NTT has nothing to optimize in your mind.\n"
},
{
"cell_type": "code",
@ -32,11 +32,11 @@
"role": "mandatory",
"difficulty": 2,
"kind": "demo",
"title": "Inspect Convolution And Negacyclic Folding"
"title": "See The Product Grid And The Diagonal Sums"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Inspect Convolution And Negacyclic Folding\n\nfrom ntt_learning.toy_ntt import negacyclic_multiply, schoolbook_convolution\n\nleft = [2, 1, 3, 0]\nright = [1, 4, 0, 2]\n\nprint(\"convolution:\", schoolbook_convolution(left, right))\nprint(\"negacyclic in x^4 + 1:\", negacyclic_multiply(left, right, n=4))\n"
"source": "# MANDATORY | difficulty 2 | See The Product Grid And The Diagonal Sums\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import convolution_contributions, schoolbook_convolution\nfrom ntt_learning.visuals import plot_convolution_grid\n\nleft = [1, 2, 3, 4]\nright = [5, 6, 7, 8]\nraw = schoolbook_convolution(left, right)\n\nprint(\"raw convolution:\", raw)\nfor row in convolution_contributions(left, right):\n print(row)\n\nfig = plot_convolution_grid(left, right, title=\"Schoolbook products for [1,2,3,4] * [5,6,7,8]\")\ndisplay(fig)\n"
},
{
"cell_type": "markdown",
@ -45,10 +45,10 @@
"role": "mandatory",
"difficulty": 2,
"kind": "explanation",
"title": "Toy NTT As A Round Trip"
"title": "Wraparound Is The First Structural Fork"
}
},
"source": "## MANDATORY | difficulty 2 | Toy NTT As A Round Trip\n\nA tiny transform is useful because it keeps every entry inspectable. The first goal is not Kyber fidelity.\nThe first goal is to see that the transform maps one coefficient view to another and can be inverted.\n"
"source": "## MANDATORY | difficulty 2 | Wraparound Is The First Structural Fork\n\nOnce the raw tail exists, the ring tells you what to do with it.\n\n- in `x^n - 1`, high-degree terms wrap back with a positive sign\n- in `x^n + 1`, high-degree terms wrap back with a sign flip\n\nThat sign flip is not cosmetic. It is exactly what makes the negacyclic story different.\n"
},
{
"cell_type": "code",
@ -58,37 +58,23 @@
"role": "mandatory",
"difficulty": 2,
"kind": "demo",
"title": "Run A Tiny Forward And Inverse NTT"
"title": "See Cyclic And Negacyclic Folding Side By Side"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Run A Tiny Forward And Inverse NTT\n\nfrom ntt_learning.toy_ntt import find_primitive_root, forward_ntt, inverse_ntt\n\nmodulus = 17\nomega = find_primitive_root(order=4, modulus=modulus)\nsignal = [3, 1, 4, 1]\nspectrum = forward_ntt(signal, modulus=modulus, omega=omega)\n\nprint(\"primitive 4th root:\", omega)\nprint(\"forward spectrum:\", spectrum)\nprint(\"inverse recovery:\", inverse_ntt(spectrum, modulus=modulus, omega=omega))\n"
"source": "# MANDATORY | difficulty 2 | See Cyclic And Negacyclic Folding Side By Side\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import negacyclic_multiply, schoolbook_convolution, wraparound_contributions\nfrom ntt_learning.visuals import plot_wraparound\n\nleft = [1, 2, 3, 4]\nright = [5, 6, 7, 8]\nraw = schoolbook_convolution(left, right)\n\nprint(\"raw convolution:\", raw)\nprint(\"negacyclic in x^4 + 1:\", negacyclic_multiply(left, right, n=4))\nprint(\"cyclic folding rows:\")\nfor row in wraparound_contributions(raw, n=4, negacyclic=False):\n print(row)\nprint(\"negacyclic folding rows:\")\nfor row in wraparound_contributions(raw, n=4, negacyclic=True):\n print(row)\n\ndisplay(plot_wraparound(raw, n=4, negacyclic=False, title=\"Cyclic folding into x^4 - 1\"))\ndisplay(plot_wraparound(raw, n=4, negacyclic=True, title=\"Negacyclic folding into x^4 + 1\"))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"difficulty": 2,
"kind": "explanation",
"title": "Butterflies Are Local Dataflow"
"title": "The Tiny Transform Teaser"
}
},
"source": "## MANDATORY | difficulty 3 | Butterflies Are Local Dataflow\n\nA butterfly is a local rewrite of a pair. The pair changes because one branch is twiddled by a zeta value.\nThis is separate from the global story about polynomial multiplication.\n\nForward Cooley-Tukey and inverse Gentleman-Sande have the same shape intuition: pair values, combine them, and move layer by layer.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Compare Pairwise And Stage-Level Butterfly Views"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Compare Pairwise And Stage-Level Butterfly Views\n\nfrom ntt_learning.toy_ntt import action_rows, apply_ct_stage, ct_butterfly_pair, gs_butterfly_pair\n\nprint(\"single CT pair:\", ct_butterfly_pair(top=7, bottom=5, zeta=3, modulus=17))\nprint(\"single GS pair:\", gs_butterfly_pair(top=7, bottom=5, zeta=3, modulus=17))\n\nvalues = [3, 1, 4, 1]\nstage_output, stage_actions = apply_ct_stage(values, block_size=2, zetas=1, modulus=17)\n\nprint(\"stage output:\", stage_output)\nprint(\"stage trace:\", action_rows(stage_actions))\n"
"source": "## MANDATORY | difficulty 2 | The Tiny Transform Teaser\n\nThe transform is not magic. It is a change of coordinates chosen so that multiplication gets easier.\n\nThe next bundle will treat the transform itself directly.\nThis first bundle only makes sure the learner can see the raw thing being optimized.\n"
},
{
"cell_type": "markdown",
@ -100,19 +86,7 @@
"title": "Retrieval Check"
}
},
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\nQuiz:\n\n1. What changes when schoolbook multiplication is folded negacyclically?\n2. Why is the toy NTT introduced before Kyber indexing details?\n3. In a butterfly, which part of the computation is local and directly inspectable?\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Extension"
}
},
"source": "## FACULTATIVE | difficulty 4 | Optional Extension\n\nIf the local pairings already feel comfortable, inspect a bit-reversed ordering next. That prepares the learner\nfor later discussions of array ordering without mixing it into the mandatory route too early.\n"
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\nAnswer in words before moving on:\n\n1. Why do diagonal sums appear in schoolbook multiplication?\n2. What is the one exact sign difference between cyclic and negacyclic folding?\n3. If you cannot track the tail wraparound, what part of the NTT story will stay vague?\n"
},
{
"cell_type": "code",
@ -122,11 +96,11 @@
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Bit-Reversed Ordering"
"title": "Optional: Compare Another Example"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Bit-Reversed Ordering\n\nfrom ntt_learning.toy_ntt import bit_reversed_order\n\nprint(bit_reversed_order([0, 1, 2, 3, 4, 5, 6, 7]))\n"
"source": "# FACULTATIVE | difficulty 4 | Optional: Compare Another Example\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import negacyclic_multiply, schoolbook_convolution\nfrom ntt_learning.visuals import plot_convolution_grid, plot_wraparound\n\nleft = [2, 1, 0, 3]\nright = [4, 0, 1, 2]\nraw = schoolbook_convolution(left, right)\n\nprint(\"raw convolution:\", raw)\nprint(\"negacyclic:\", negacyclic_multiply(left, right, n=4))\ndisplay(plot_convolution_grid(left, right, title=\"A second schoolbook grid\"))\ndisplay(plot_wraparound(raw, n=4, negacyclic=True, title=\"A second negacyclic fold\"))\n"
},
{
"cell_type": "markdown",
@ -152,14 +126,35 @@
},
"ntt_learning": {
"title": "Lecture: Convolution To Toy NTT",
"contract_version": "0.1",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb"
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},

View file

@ -10,7 +10,7 @@
"title": "Problem Set Goals"
}
},
"source": "## META | difficulty 1 | Problem Set Goals\n\nUse this notebook to check retrieval, not to discover the topic for the first time.\nIf the questions feel opaque, return to the lecture and lab first.\n"
"source": "## META | difficulty 1 | Problem Set Goals\n\nThis notebook checks whether the multiplication and folding pictures are now stable in memory.\n"
},
{
"cell_type": "markdown",
@ -22,7 +22,7 @@
"title": "Multiple-Choice Retrieval"
}
},
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. Negacyclic reduction mainly changes:\n A. coefficient labels only\n B. wraparound terms by folding them back with sign changes\n C. the modulus but not the polynomial ring\n\n2. The toy NTT is introduced early because it:\n A. already matches Kyber implementation details exactly\n B. removes the need to inspect arrays\n C. gives a small, reversible transform that can be inspected directly\n\n3. A butterfly stage is best thought of as:\n A. a local rewrite over paired entries\n B. a proof that convolution is impossible\n C. a random permutation with no arithmetic structure\n"
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. The diagonal sums in schoolbook multiplication come from:\n A. random coincidence\n B. grouping terms with the same final degree\n C. bit-reversal\n\n2. Negacyclic folding differs from cyclic folding because:\n A. the wrapped tail flips sign\n B. the polynomial degrees disappear\n C. the raw convolution gets shorter before folding\n\n3. The main reason to study the raw grid before NTT is:\n A. because the transform is impossible otherwise\n B. because it makes the optimized algorithm visually grounded\n C. because Kyber never uses transforms\n"
},
{
"cell_type": "code",
@ -36,7 +36,33 @@
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {\n 1: \"B\",\n 2: \"C\",\n 3: \"A\",\n}\n\nprint(answers)\n"
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {1: \"B\", 2: \"A\", 3: \"B\"}\nprint(answers)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Manual Fold Check"
}
},
"source": "## MANDATORY | difficulty 2 | Manual Fold Check\n\nCompute the negacyclic fold of the raw vector `[5, 16, 34, 60, 61, 52, 32]` into `x^4 + 1` by hand before running the next cell.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Check The Fold"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Check The Fold\n\nfrom ntt_learning.toy_ntt import negacyclic_reduce\n\nraw = [5, 16, 34, 60, 61, 52, 32]\nprint(negacyclic_reduce(raw, n=4))\n"
},
{
"cell_type": "markdown",
@ -48,24 +74,11 @@
"title": "Written Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nReflection prompt:\n\n- In one paragraph, separate the algebraic purpose of the NTT from the local butterfly dataflow.\n- In one sentence, explain why the course postpones Kyber-specific indexing.\n"
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nIn one paragraph, explain why \u201cwrap the tail back\u201d is still too vague unless you also specify:\n\n- the divisor\n- the target slot\n- the sign rule\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Verify A Round Trip"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Verify A Round Trip\n\nfrom ntt_learning.toy_ntt import find_primitive_root, forward_ntt, inverse_ntt\n\nsignal = [3, 1, 4, 1]\nmodulus = 17\nomega = find_primitive_root(order=4, modulus=modulus)\nrecovered = inverse_ntt(forward_ntt(signal, modulus=modulus, omega=omega), modulus=modulus, omega=omega)\n\nassert recovered == signal\nprint(\"round-trip verified:\", recovered)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "facultative",
@ -74,21 +87,8 @@
"title": "Optional Challenge"
}
},
"source": "## FACULTATIVE | difficulty 4 | Optional Challenge\n\nTry replacing the signal with your own four coefficients and predict the forward spectrum before running the next cell.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Explore Another Signal"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Explore Another Signal\n\nfrom ntt_learning.toy_ntt import find_primitive_root, forward_ntt\n\nsignal = [6, 0, 5, 2]\nmodulus = 17\nomega = find_primitive_root(order=4, modulus=modulus)\n\nprint(\"spectrum:\", forward_ntt(signal, modulus=modulus, omega=omega))\n"
"source": "# FACULTATIVE | difficulty 4 | Optional Challenge\n\nfrom ntt_learning.toy_ntt import wraparound_contributions\n\nraw = [3, 11, 7, 0, 5, 9, 4]\nfor row in wraparound_contributions(raw, n=4, negacyclic=True):\n print(row)\n"
},
{
"cell_type": "markdown",
@ -114,14 +114,35 @@
},
"ntt_learning": {
"title": "Problems: Convolution To Toy NTT",
"contract_version": "0.1",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb"
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},

View file

@ -10,7 +10,7 @@
"title": "Studio Goals"
}
},
"source": "## META | difficulty 1 | Studio Goals\n\nThis studio frames implementation reading.\n\nKeep three lenses separate:\n\n- algebraic purpose\n- array dataflow\n- protocol-specific conventions\n"
"source": "## META | difficulty 1 | Studio Goals\n\nThis studio is about comparison and diagnosis.\nThe learner should leave with a strong visual distinction between cyclic and negacyclic wraparound.\n"
},
{
"cell_type": "markdown",
@ -19,10 +19,10 @@
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Forward And Inverse Flow Side By Side"
"title": "Two Folds, Same Raw Tail, Different Result"
}
},
"source": "## MANDATORY | difficulty 3 | Forward And Inverse Flow Side By Side\n\nThe goal here is not to claim the same cell-by-cell formula for both directions.\nThe goal is to compare the same pairing structure while noticing that forward and inverse flows push arithmetic in opposite directions.\n"
"source": "## MANDATORY | difficulty 3 | Two Folds, Same Raw Tail, Different Result\n\nIf the raw convolution is fixed, the only thing that changes is the ring rule.\nThat is exactly why the same tail can produce two different reduced polynomials.\n"
},
{
"cell_type": "code",
@ -32,11 +32,11 @@
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Compare Cooley-Tukey And Gentleman-Sande Views"
"title": "Compare The Two Fold Rules"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Compare Cooley-Tukey And Gentleman-Sande Views\n\nfrom ntt_learning.toy_ntt import action_rows, apply_ct_stage, apply_gs_stage\n\nvalues = [2, 5, 7, 1, 3, 6, 4, 0]\nct_output, ct_actions = apply_ct_stage(values, block_size=4, zetas=[1, 4, 1, 4], modulus=17)\ngs_output, gs_actions = apply_gs_stage(values, block_size=4, zetas=[1, 4, 1, 4], modulus=17)\n\nprint(\"input:\", values)\nprint(\"ct output:\", ct_output)\nprint(\"gs output:\", gs_output)\nprint(\"ct trace:\", action_rows(ct_actions))\nprint(\"gs trace:\", action_rows(gs_actions))\n"
"source": "# MANDATORY | difficulty 3 | Compare The Two Fold Rules\n\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import schoolbook_convolution\nfrom ntt_learning.visuals import plot_wraparound\n\nraw = schoolbook_convolution([1, 2, 3, 4], [5, 6, 7, 8])\nprint(\"raw convolution:\", raw)\ndisplay(plot_wraparound(raw, n=4, negacyclic=False, title=\"Positive wrap into x^4 - 1\"))\ndisplay(plot_wraparound(raw, n=4, negacyclic=True, title=\"Negative wrap into x^4 + 1\"))\n"
},
{
"cell_type": "markdown",
@ -48,7 +48,7 @@
"title": "Debug Checklist"
}
},
"source": "## MANDATORY | difficulty 2 | Debug Checklist\n\nWhen a stage looks wrong, inspect these in order:\n\n1. wrong pairings\n2. wrong zeta value\n3. wrong sign in the subtraction branch\n4. wrong direction choice between forward-style and inverse-style flow\n"
"source": "## MANDATORY | difficulty 2 | Debug Checklist\n\nIf a wraparound result looks wrong, inspect these in order:\n\n1. Was the raw convolution itself correct?\n2. Was the divisor `x^n - 1` or `x^n + 1`?\n3. Did the wrapped tail land in the right slot?\n4. Did the sign flip happen on the wrapped term?\n"
},
{
"cell_type": "code",
@ -58,23 +58,23 @@
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Compare Baseline And Wrong-Zeta Output"
"title": "See A Wrong-Sign Failure"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Compare Baseline And Wrong-Zeta Output\n\nfrom ntt_learning.toy_ntt import apply_ct_stage\n\nvalues = [3, 1, 4, 1]\nbaseline, _ = apply_ct_stage(values, block_size=2, zetas=1, modulus=17)\nwrong_zeta, _ = apply_ct_stage(values, block_size=2, zetas=3, modulus=17)\n\nprint(\"baseline:\", baseline)\nprint(\"wrong zeta:\", wrong_zeta)\n"
"source": "# MANDATORY | difficulty 2 | See A Wrong-Sign Failure\n\nfrom ntt_learning.toy_ntt import schoolbook_convolution, negacyclic_reduce\n\nraw = schoolbook_convolution([1, 2, 3, 4], [5, 6, 7, 8])\nwrong = [raw[0] + raw[4], raw[1] + raw[5], raw[2] + raw[6], raw[3]]\n\nprint(\"raw:\", raw)\nprint(\"wrong sign fold:\", wrong)\nprint(\"correct negacyclic fold:\", negacyclic_reduce(raw, n=4))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Ordering Preview"
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## FACULTATIVE | difficulty 4 | Optional Ordering Preview\n\nBit-reversal is important later, but it is deliberately optional here so the main route can stay focused on pair mechanics first.\n"
"source": "## MANDATORY | difficulty 2 | Reflection\n\nExplain the exact visual difference between \u201cthe wrong-sign fold\u201d and \u201cthe correct negacyclic fold\u201d.\n"
},
{
"cell_type": "code",
@ -84,11 +84,11 @@
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Inspect Bit-Reversed Order"
"title": "Optional: Fold A Larger Tail"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Inspect Bit-Reversed Order\n\nfrom ntt_learning.toy_ntt import bit_reversed_order\n\nprint(bit_reversed_order([0, 1, 2, 3, 4, 5, 6, 7]))\n"
"source": "# FACULTATIVE | difficulty 4 | Optional: Fold A Larger Tail\n\nfrom IPython.display import display\n\nfrom ntt_learning.visuals import plot_wraparound\n\nraw = [4, 8, 12, 16, 9, 5, 1, 7, 11]\ndisplay(plot_wraparound(raw, n=4, negacyclic=True, title=\"Longer tail, same fold rule\"))\n"
},
{
"cell_type": "markdown",
@ -100,7 +100,7 @@
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: return to `../../COURSE_BLUEPRINT.ipynb` and extend the course into Kyber-specific notebooks.\n"
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `../../../foundations/02_negative_wrapped_ntt/lecture.ipynb`\n"
}
],
"metadata": {
@ -114,14 +114,35 @@
},
"ntt_learning": {
"title": "Studio: Convolution To Toy NTT",
"contract_version": "0.1",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb"
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},

View file

@ -0,0 +1,165 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Lab Goals"
}
},
"source": "## META | difficulty 1 | Lab Goals\n\nThe lab is about prediction inside the direct transform matrix.\nDo not run the next cells until you name the powers and products you expect to matter.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Exercise 1"
}
},
"source": "## MANDATORY | difficulty 2 | Exercise 1\n\nFor `signal = [1,2,3,4]`, `n = 4`, `q = 17`, predict:\n\n- which powers of `\u03c8` appear in row `j = 1`\n- whether the inverse should need both `\u03c8^-1` and `n^-1`\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Prediction Check"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Prediction Check\n\nfrom ntt_learning.toy_ntt import find_psi, ntt_psi_exponent_grid\n\npsi = find_psi(4, 17)\nprint(\"psi:\", psi)\nfor row_index, row in enumerate(ntt_psi_exponent_grid(4)):\n print(\"row\", row_index, row)\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Interactive Signal Explorer"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Interactive Signal Explorer\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import find_psi, forward_ntt_psi, inverse_ntt_psi\n\nmodulus = 17\npsi = find_psi(4, modulus)\n\ndef preview(a0=1, a1=2, a2=3, a3=4):\n signal = [a0, a1, a2, a3]\n spectrum = forward_ntt_psi(signal, modulus, psi)\n print(\"signal:\", signal)\n print(\"spectrum:\", spectrum)\n print(\"inverse:\", inverse_ntt_psi(spectrum, modulus, psi))\n\ndisplay(\n widgets.interact(\n preview,\n a0=(0, 16),\n a1=(0, 16),\n a2=(0, 16),\n a3=(0, 16),\n )\n)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Exercise 2"
}
},
"source": "## MANDATORY | difficulty 2 | Exercise 2\n\nExplain what the pointwise multiplication in the transform domain is buying you.\nUse the words \u201creplace convolution by slotwise multiplication\u201d in your own sentence.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Compare Raw Convolution And Slotwise Multiplication"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Compare Raw Convolution And Slotwise Multiplication\n\nfrom ntt_learning.toy_ntt import (\n find_psi,\n forward_ntt_psi,\n inverse_ntt_psi,\n pointwise_multiply,\n schoolbook_convolution,\n)\n\nleft = [2, 1, 0, 3]\nright = [4, 0, 1, 2]\npsi = find_psi(4, 17)\nleft_hat = forward_ntt_psi(left, 17, psi)\nright_hat = forward_ntt_psi(right, 17, psi)\n\nprint(\"schoolbook raw:\", schoolbook_convolution(left, right))\nprint(\"left_hat:\", left_hat)\nprint(\"right_hat:\", right_hat)\nprint(\"pointwise product:\", pointwise_multiply(left_hat, right_hat, 17))\nprint(\"inverse:\", inverse_ntt_psi(pointwise_multiply(left_hat, right_hat, 17), 17, psi))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- What feels concrete in the direct transform matrix?\n- What still feels too expensive or repetitive?\n- Why are butterflies the obvious next step?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Try A Different Modulus"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Try A Different Modulus\n\nfrom ntt_learning.toy_ntt import find_psi, forward_ntt_psi\n\nmodulus = 97\npsi = find_psi(4, modulus)\nsignal = [1, 2, 3, 4]\n\nprint(\"modulus:\", modulus)\nprint(\"psi:\", psi)\nprint(\"spectrum:\", forward_ntt_psi(signal, modulus, psi))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `problems.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lab: Negative-Wrapped NTT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,165 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Objectives"
}
},
"source": "## META | difficulty 1 | Objectives\n\nThis bundle introduces the transform itself in its negacyclic form.\n\nFocus:\n\n- the difference between `\u03c9` and `\u03c8`\n- direct NTT\u03c8 and INTT\u03c8\n- the direct convolution theorem in the negacyclic setting\n- why this is still too slow at `O(n^2)` without butterflies\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "explanation",
"title": "Why \u03c8 Shows Up"
}
},
"source": "## MANDATORY | difficulty 2 | Why \u03c8 Shows Up\n\nFor negative-wrapped convolution, the clean transform formula uses a `2n`-th root `\u03c8` with:\n\n- `\u03c8^2 = \u03c9`\n- `\u03c8^n = -1`\n\nThat is what bakes the negacyclic sign rule into the transform itself.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "demo",
"title": "Inspect \u03c9, \u03c8, And The Direct Transform Matrix"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Inspect \u03c9, \u03c8, And The Direct Transform Matrix\n\nfrom ntt_learning.toy_ntt import find_primitive_root, find_psi, ntt_psi_exponent_grid, ntt_psi_matrix\n\nmodulus = 17\nn = 4\nomega = find_primitive_root(n, modulus)\npsi = find_psi(n, modulus)\n\nprint(\"omega:\", omega)\nprint(\"psi:\", psi)\nprint(\"exponent grid:\")\nfor row in ntt_psi_exponent_grid(n):\n print(row)\nprint(\"NTT_psi matrix:\")\nfor row in ntt_psi_matrix(n, modulus, psi):\n print(row)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "explanation",
"title": "Direct NTT\u03c8 Is Mechanically Clear But Still Quadratic"
}
},
"source": "## MANDATORY | difficulty 2 | Direct NTT\u03c8 Is Mechanically Clear But Still Quadratic\n\nThe direct transform is useful because every coefficient and every exponent is visible.\nIt is not yet efficient. It still performs the full matrix multiplication.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "demo",
"title": "Run A Direct NTT\u03c8 / INTT\u03c8 Round Trip"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Run A Direct NTT\u03c8 / INTT\u03c8 Round Trip\n\nfrom ntt_learning.toy_ntt import find_psi, forward_ntt_psi, inverse_ntt_psi\n\nsignal = [1, 2, 3, 4]\nmodulus = 17\npsi = find_psi(len(signal), modulus)\nspectrum = forward_ntt_psi(signal, modulus, psi)\n\nprint(\"signal:\", signal)\nprint(\"spectrum:\", spectrum)\nprint(\"inverse recovery:\", inverse_ntt_psi(spectrum, modulus, psi))\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Use Direct NTT\u03c8 For Negacyclic Multiplication"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Use Direct NTT\u03c8 For Negacyclic Multiplication\n\nfrom ntt_learning.toy_ntt import find_psi, forward_ntt_psi, inverse_ntt_psi, negacyclic_multiply, pointwise_multiply\n\nleft = [1, 2, 3, 4]\nright = [5, 6, 7, 8]\nmodulus = 17\npsi = find_psi(4, modulus)\n\nleft_hat = forward_ntt_psi(left, modulus, psi)\nright_hat = forward_ntt_psi(right, modulus, psi)\nproduct_hat = pointwise_multiply(left_hat, right_hat, modulus)\n\nprint(\"NTT_psi(left):\", left_hat)\nprint(\"NTT_psi(right):\", right_hat)\nprint(\"pointwise product:\", product_hat)\nprint(\"inverse of pointwise product:\", inverse_ntt_psi(product_hat, modulus, psi))\nprint(\"schoolbook negacyclic:\", negacyclic_multiply(left, right, n=4, modulus=modulus))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Retrieval Check"
}
},
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\n1. Why is `\u03c8` stronger than `\u03c9` in the negacyclic story?\n2. What exact property does the inverse add that the forward transform does not?\n3. Why are we still dissatisfied after seeing the direct transform work correctly?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Compare Positive And Negative Wrapped Transforms"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Compare Positive And Negative Wrapped Transforms\n\nfrom ntt_learning.toy_ntt import find_primitive_root, find_psi, forward_ntt, forward_ntt_psi\n\nsignal = [1, 2, 3, 4]\nmodulus = 17\nomega = find_primitive_root(4, modulus)\npsi = find_psi(4, modulus)\n\nprint(\"positive-wrapped NTT:\", forward_ntt(signal, modulus, omega))\nprint(\"negative-wrapped NTT_psi:\", forward_ntt_psi(signal, modulus, psi))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `lab.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lecture: Negative-Wrapped NTT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Problem Set Goals"
}
},
"source": "## META | difficulty 1 | Problem Set Goals\n\nThis notebook checks whether the direct negative-wrapped transform is now mechanically understandable.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Multiple-Choice Retrieval"
}
},
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. In the negacyclic transform, `\u03c8` matters because:\n A. it makes the modulus disappear\n B. it encodes the `x^n + 1` sign structure\n C. it avoids all inverses\n\n2. The inverse transform differs from the forward transform by:\n A. an inverse root and an `n^-1` scaling\n B. a larger modulus\n C. removing all twiddle factors\n\n3. The direct matrix transform is still pedagogically useful because:\n A. it keeps every coefficient contribution visible\n B. it is how Kyber is implemented directly at full size\n C. it removes the need for butterflies\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Answer Key"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {1: \"B\", 2: \"A\", 3: \"A\"}\nprint(answers)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Round-Trip Check"
}
},
"source": "## MANDATORY | difficulty 2 | Round-Trip Check\n\nVerify by code that `INTT\u03c8(NTT\u03c8(a)) = a` for a nontrivial vector.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Check The Round Trip"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Check The Round Trip\n\nfrom ntt_learning.toy_ntt import find_psi, forward_ntt_psi, inverse_ntt_psi\n\nsignal = [6, 0, 5, 2]\npsi = find_psi(4, 17)\nrecovered = inverse_ntt_psi(forward_ntt_psi(signal, 17, psi), 17, psi)\nprint(recovered)\nassert recovered == signal\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Written Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nIn one paragraph, explain why the direct transform is the right place to understand the algebra, but not the right place to stop if you care about algorithmic speed.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Challenge"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional Challenge\n\nfrom ntt_learning.toy_ntt import find_psi, forward_ntt_psi\n\npsi = find_psi(4, 17)\nfor signal in ([1, 1, 1, 1], [0, 1, 0, 1], [3, 5, 7, 9]):\n print(signal, \"->\", forward_ntt_psi(signal, 17, psi))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `studio.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Problems: Negative-Wrapped NTT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Studio Goals"
}
},
"source": "## META | difficulty 1 | Studio Goals\n\nThe studio compares direct positive-wrapped and negative-wrapped transforms so the learner stops treating \u201cNTT\u201d as one unqualified object.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Same Input, Different Transform Story"
}
},
"source": "## MANDATORY | difficulty 3 | Same Input, Different Transform Story\n\nThe same coefficient vector can be sent through two different transform stories depending on the quotient ring.\nThat difference is not implementation noise. It is structural.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Compare Positive-Wrapped And Negative-Wrapped Views"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Compare Positive-Wrapped And Negative-Wrapped Views\n\nfrom ntt_learning.toy_ntt import find_primitive_root, find_psi, forward_ntt, forward_ntt_psi\n\nsignal = [1, 2, 3, 4]\nmodulus = 17\nomega = find_primitive_root(4, modulus)\npsi = find_psi(4, modulus)\n\nprint(\"positive-wrapped NTT:\", forward_ntt(signal, modulus, omega))\nprint(\"negative-wrapped NTT_psi:\", forward_ntt_psi(signal, modulus, psi))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Debug Checklist"
}
},
"source": "## MANDATORY | difficulty 2 | Debug Checklist\n\nIf a direct transform result looks suspicious, inspect:\n\n1. the chosen modulus\n2. the order of the root\n3. whether you are using `\u03c9` or `\u03c8`\n4. whether the inverse includes `n^-1`\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "See A Wrong-Root Failure"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | See A Wrong-Root Failure\n\nfrom ntt_learning.toy_ntt import find_primitive_root, find_psi, forward_ntt_psi\n\nsignal = [1, 2, 3, 4]\nmodulus = 17\nomega = find_primitive_root(4, modulus)\npsi = find_psi(4, modulus)\n\nprint(\"correct psi-based transform:\", forward_ntt_psi(signal, modulus, psi))\nprint(\"wrongly using omega as if it were psi:\", forward_ntt_psi(signal, modulus, omega))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nExplain why \u201cpick any root of unity\u201d is not an acceptable habit in this subject.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Matrix Comparison"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Matrix Comparison\n\nfrom ntt_learning.toy_ntt import find_primitive_root, find_psi, ntt_psi_matrix\n\nmodulus = 17\nomega = find_primitive_root(4, modulus)\npsi = find_psi(4, modulus)\n\nprint(\"omega:\", omega)\nprint(\"psi:\", psi)\nfor row in ntt_psi_matrix(4, modulus, psi):\n print(row)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `../../../butterfly_mechanics/03_fast_forward_ct/lecture.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Studio: Negative-Wrapped NTT",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Lab Goals"
}
},
"source": "## META | difficulty 1 | Lab Goals\n\nThe lab is about making the Kyber modulus obstruction explicit enough that it becomes memorable.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 1"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 1\n\nBefore running the next cell, say out loud:\n\n- why `256 | 3328`\n- why `512` does not divide `3328`\n- what that means for the existence of `\u03c8`\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Prediction Check"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Prediction Check\n\nprint(\"3328 / 256 =\", (3329 - 1) // 256)\nprint(\"3328 % 256 =\", (3329 - 1) % 256)\nprint(\"3328 % 512 =\", (3329 - 1) % 512)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 2"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 2\n\nVerify the toy base multiplication by hand before running the next cell.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Check The Toy Base Multiplication"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Check The Toy Base Multiplication\n\nfrom ntt_learning.toy_ntt import base_multiply_pair\n\nprint(base_multiply_pair([3, 5], [2, 7], zeta=6, modulus=17))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- Why is \u201cthere is no 512-th root\u201d not just a technical footnote?\n- What false picture of Kyber would survive if you ignored that fact?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Tiny Modulus Classifier"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Tiny Modulus Classifier\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\ndef classify(q=17, n=4):\n print({\"q\": q, \"n\": n, \"pwc\": (q - 1) % n == 0, \"nwc\": (q - 1) % (2 * n) == 0})\n\ndisplay(widgets.interact(classify, q=(5, 101), n=(2, 16)))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `problems.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lab: Kyber NTT And Base Multiplication",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,177 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Objectives"
}
},
"source": "## META | difficulty 1 | Objectives\n\nThis bundle ties the transform story to Kyber without throwing away the concrete arithmetic.\n\nFocus:\n\n- what Kyber\u2019s `q = 3329`, `n = 256` really allow\n- why the full `2n`-th root mental model breaks at Kyber v3\n- why base multiplication appears\n- how to keep the story NTT-centered without lying about the modulus\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Kyber Is Not \u201cJust Generic Negacyclic NTT With Big Numbers\u201d"
}
},
"source": "## MANDATORY | difficulty 3 | Kyber Is Not \u201cJust Generic Negacyclic NTT With Big Numbers\u201d\n\nThe key arithmetic reality is:\n\n- Kyber v3 has `n = 256`\n- `q = 3329`\n- `256` divides `3328`\n- `512` does **not** divide `3328`\n\nThat means a primitive `256`-th root exists, but a primitive `512`-th root does not.\nSo the clean full-length `\u03c8` story from the toy negative-wrapped transform does not lift over unchanged.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Check The Kyber Root Reality Directly"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Check The Kyber Root Reality Directly\n\nfrom ntt_learning.toy_ntt import find_primitive_root\n\nprint(\"3329 - 1 =\", 3329 - 1)\nprint(\"primitive 256-th root in Z_3329:\", find_primitive_root(256, 3329))\ntry:\n find_primitive_root(512, 3329)\nexcept Exception as exc:\n print(\"512-th root fails exactly because:\", exc)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Tiny Analogue Of The Same Obstruction"
}
},
"source": "## MANDATORY | difficulty 3 | Tiny Analogue Of The Same Obstruction\n\nThe easiest way to feel this is to shrink the numbers.\nIn `Z_13`, a 4-th root exists because `4 | 12`, but an 8-th root does not because `8` does not divide `12`.\n\nThat is the same shape of obstruction as Kyber v3, only tiny enough to inspect instantly.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Run The Tiny Analogue"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Run The Tiny Analogue\n\nfrom ntt_learning.toy_ntt import find_primitive_root\n\nprint(\"primitive 4-th root in Z_13:\", find_primitive_root(4, 13))\ntry:\n find_primitive_root(8, 13)\nexcept Exception as exc:\n print(\"8-th root fails:\", exc)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Why Base Multiplication Appears"
}
},
"source": "## MANDATORY | difficulty 3 | Why Base Multiplication Appears\n\nOnce the ring does not split into fully scalar transform slots in the naive `\u03c8` way, multiplication in the transform domain is no longer \u201cjust multiply scalars slot by slot\u201d.\n\nA small block structure remains, and that is why pairwise base multiplication appears.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "See A Toy Base Multiplication"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | See A Toy Base Multiplication\n\nfrom ntt_learning.toy_ntt import base_multiply_pair\n\nleft = [7, 11]\nright = [5, 13]\nzeta = 4\nmodulus = 17\n\nraw = [left[0] * right[0], left[0] * right[1] + left[1] * right[0], left[1] * right[1]]\nreduced = [(raw[0] + zeta * raw[2]) % modulus, raw[1] % modulus]\n\nprint(\"raw degree-2 product:\", raw)\nprint(\"reduce with x^2 = zeta:\", reduced)\nprint(\"base_multiply_pair:\", base_multiply_pair(left, right, zeta, modulus))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Retrieval Check"
}
},
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\n1. What exact divisibility fact blocks the naive full `2n`-th root story in Kyber v3?\n2. Why does that obstruction point you toward base multiplication?\n3. Why would it be misleading to teach Kyber as if the toy `\u03c8` story carried over unchanged?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Compare Several Moduli"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Compare Several Moduli\n\ndef status(n, q):\n pwc = (q - 1) % n == 0\n nwc = (q - 1) % (2 * n) == 0\n return {\"n\": n, \"q\": q, \"pwc\": pwc, \"nwc\": nwc}\n\nfor sample in [(4, 17), (4, 13), (256, 7681), (256, 3329)]:\n print(status(*sample))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `lab.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lecture: Kyber NTT And Base Multiplication",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Problem Set Goals"
}
},
"source": "## META | difficulty 1 | Problem Set Goals\n\nThis notebook checks whether the Kyber-specific modulus story is now precise instead of fuzzy.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Multiple-Choice Retrieval"
}
},
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. For Kyber v3, the important root fact is:\n A. `512` divides `3328`\n B. `256` divides `3328` but `512` does not\n C. no relevant root exists at all\n\n2. Base multiplication appears because:\n A. the transform-domain multiplication remains structured in small blocks\n B. scalar multiplication is forbidden in finite fields\n C. schoolbook multiplication vanished\n\n3. The biggest pedagogical risk is:\n A. teaching Kyber as if the toy `\u03c8` model applied unchanged\n B. teaching any toy examples at all\n C. comparing moduli\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Answer Key"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {1: \"B\", 2: \"A\", 3: \"A\"}\nprint(answers)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Quick Check"
}
},
"source": "## MANDATORY | difficulty 2 | Quick Check\n\nExplain in one sentence why a primitive 256-th root is not enough to recover the full toy `\u03c8` story at `n = 256`.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Numerical Check"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Numerical Check\n\nprint(\"2 * 256 =\", 2 * 256)\nprint(\"3329 - 1 =\", 3329 - 1)\nprint(\"divides?\", (3329 - 1) % (2 * 256) == 0)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Written Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nIn one paragraph, explain why the Kyber notebook belongs after the toy direct and fast-transform notebooks rather than before them.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Challenge"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional Challenge\n\nsamples = [(256, 7681), (256, 3329), (8, 97), (8, 41)]\nfor n, q in samples:\n print({\"n\": n, \"q\": q, \"n_divides_q_minus_1\": (q - 1) % n == 0, \"two_n_divides_q_minus_1\": (q - 1) % (2 * n) == 0})\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `studio.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Problems: Kyber NTT And Base Multiplication",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Studio Goals"
}
},
"source": "## META | difficulty 1 | Studio Goals\n\nThis studio compares the clean toy story with the Kyber-specific modulus reality and treats the mismatch as the lesson.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "The Mismatch Is Not A Bug In The Course"
}
},
"source": "## MANDATORY | difficulty 3 | The Mismatch Is Not A Bug In The Course\n\nThe mismatch between \u201ctoy full `\u03c8` story\u201d and \u201cKyber v3 modulus reality\u201d is exactly what the learner needs to understand.\n\nThat mismatch is why base multiplication and implementation-specific scheduling matter.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Toy Full \u03c8 Story vs Kyber Root Reality"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Toy Full \u03c8 Story vs Kyber Root Reality\n\nfrom ntt_learning.toy_ntt import find_psi\n\nprint(\"toy n=4, q=17 has psi:\", find_psi(4, 17))\ntry:\n find_psi(256, 3329)\nexcept Exception as exc:\n print(\"Kyber v3 does not have that full psi story:\", exc)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Debug Checklist"
}
},
"source": "## MANDATORY | difficulty 2 | Debug Checklist\n\nIf somebody says \u201cKyber is just the same toy negative-wrapped NTT with bigger numbers\u201d, inspect:\n\n1. whether they checked `2n | q - 1`\n2. whether they accounted for the missing `\u03c8`\n3. whether they know why base multiplication appears\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "See The Exact Obstruction Again"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | See The Exact Obstruction Again\n\nq = 3329\nn = 256\nprint({\"q_minus_1\": q - 1, \"n\": n, \"2n\": 2 * n, \"q_minus_1_mod_n\": (q - 1) % n, \"q_minus_1_mod_2n\": (q - 1) % (2 * n)})\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nExplain why the Kyber base-multiplication story is easier to trust once you have already internalized the toy negacyclic transform.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Another toy base multiplication"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Another toy base multiplication\n\nfrom ntt_learning.toy_ntt import base_multiply_pair\n\nprint(base_multiply_pair([7, 9], [4, 6], zeta=11, modulus=17))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `../../../professional/06_debugging_ntt_failures/lecture.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Studio: Kyber NTT And Base Multiplication",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Lab Goals"
}
},
"source": "## META | difficulty 1 | Lab Goals\n\nThe lab asks you to match output fingerprints to the underlying bug.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 1"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 1\n\nBefore running the next cell, predict which of these bug labels goes with each fingerprint:\n\n- shuffled but otherwise familiar values\n- values that look uniformly too large\n- values broken already in a local stage pair\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Prediction Check"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Prediction Check\n\nfingerprints = {\n \"wrong_order\": \"shuffled but same value set\",\n \"missing_scale\": \"same shape but uniformly off by a factor\",\n \"wrong_zeta\": \"local pair outputs go bad immediately\",\n}\nprint(fingerprints)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "Exercise 2"
}
},
"source": "## MANDATORY | difficulty 3 | Exercise 2\n\nExplain why \u201calmost right\u201d is a useless debugging description unless you also specify whether the issue is:\n\n- sign\n- order\n- zeta\n- scaling\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "exercise",
"title": "A Small Debugging Drill"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | A Small Debugging Drill\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace, fast_ntt_psi_ct_trace\n\nforward_trace = fast_ntt_psi_ct_trace([5, 6, 7, 8], 7681, 1925)\ninverse_trace = fast_intt_psi_gs_trace(forward_trace.raw_output, 7681, 1925)\n\nprint(\"forward BO output:\", forward_trace.raw_output)\nprint(\"forward NO output:\", forward_trace.normal_order_output)\nprint(\"inverse unscaled:\", inverse_trace.raw_output)\nprint(\"inverse scaled:\", inverse_trace.scaled_output)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Reflection\n\nReflection prompt:\n\n- Which bug fingerprint feels easiest to recognize now?\n- Which one still needs more repetition?\n- What is your debugging order of operations now?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Build Your Own Fingerprint Table"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Build Your Own Fingerprint Table\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\ndef note_bug(mode=\"wrong_order\"):\n print({\"mode\": mode, \"what_to_check_first\": {\"wrong_order\": \"bit reversal\", \"missing_scale\": \"n^-1\", \"wrong_zeta\": \"local pair twiddle\", \"wrong_sign\": \"negacyclic fold sign\"}[mode]})\n\ndisplay(widgets.interact(note_bug, mode=[\"wrong_order\", \"missing_scale\", \"wrong_zeta\", \"wrong_sign\"]))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `problems.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lab: Debugging NTT Failures",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,139 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Objectives"
}
},
"source": "## META | difficulty 1 | Objectives\n\nThis final bundle turns common failure modes into visible patterns instead of vague warnings.\n\nFocus:\n\n- wrong sign in wraparound\n- wrong root or wrong zeta\n- wrong BO / NO comparison\n- missing final scaling\n- wrong mental model for the Kyber modulus\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "Bad Outputs Have Fingerprints"
}
},
"source": "## MANDATORY | difficulty 3 | Bad Outputs Have Fingerprints\n\nDebugging NTTs is easier when you stop staring at the final vector as one blob.\nEach common mistake leaves a characteristic fingerprint:\n\n- wrong sign flips specific wrapped slots\n- wrong order makes a correct value set appear shuffled\n- missing `n^-1` keeps the shape but scales everything wrong\n- wrong zeta corrupts local pair structure early\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "See Four Failure Modes Side By Side"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | See Four Failure Modes Side By Side\n\nfrom ntt_learning.toy_ntt import (\n fast_intt_psi_gs_trace,\n fast_ntt_psi_ct_trace,\n forward_ntt_psi,\n negacyclic_reduce,\n schoolbook_convolution,\n)\n\nsignal = [1, 2, 3, 4]\nforward_trace = fast_ntt_psi_ct_trace(signal, 7681, 1925)\ninverse_trace = fast_intt_psi_gs_trace(forward_trace.raw_output, 7681, 1925)\n\nraw = schoolbook_convolution([1, 2, 3, 4], [5, 6, 7, 8])\nwrong_sign = [raw[0] + raw[4], raw[1] + raw[5], raw[2] + raw[6], raw[3]]\nwrong_order = list(forward_trace.raw_output)\nwrong_scale = list(inverse_trace.raw_output)\nwrong_root = forward_ntt_psi(signal, 7681, 3383)\n\nprint(\"wrong sign fold:\", wrong_sign)\nprint(\"correct sign fold:\", negacyclic_reduce(raw, n=4))\nprint(\"wrong BO-vs-NO comparison:\", wrong_order)\nprint(\"correct NO output:\", forward_trace.normal_order_output)\nprint(\"missing final scaling:\", wrong_scale)\nprint(\"wrong root in direct transform:\", wrong_root)\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Interactive Failure Picker"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Interactive Failure Picker\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace, fast_ntt_psi_ct_trace, forward_ntt_psi\n\nforward_trace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\ninverse_trace = fast_intt_psi_gs_trace(forward_trace.raw_output, 7681, 1925)\n\nfailures = {\n \"wrong_order\": list(forward_trace.raw_output),\n \"correct_order\": list(forward_trace.normal_order_output),\n \"missing_scale\": list(inverse_trace.raw_output),\n \"scaled\": list(inverse_trace.scaled_output),\n \"wrong_root\": forward_ntt_psi([1, 2, 3, 4], 7681, 3383),\n}\n\ndef preview(mode=\"wrong_order\"):\n print(mode, \"->\", failures[mode])\n\ndisplay(widgets.interact(preview, mode=sorted(failures)))\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Retrieval Check"
}
},
"source": "## MANDATORY | difficulty 2 | Retrieval Check\n\n1. Which mistake keeps the general shape of the inverse output but leaves every entry too large by a shared factor?\n2. Which mistake often disappears once you apply the correct BO -> NO reorder?\n3. Which mistake shows up earliest in local pair traces rather than only at the very end?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Trace Rows For Debugging"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Trace Rows For Debugging\n\nfrom ntt_learning.toy_ntt import fast_ntt_psi_ct_trace, stage_rows\n\ntrace = fast_ntt_psi_ct_trace([1, 2, 3, 4], 7681, 1925)\nfor stage in trace.stages:\n print(\"stage\", stage.stage_index)\n for row in stage_rows(stage):\n print(row)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `lab.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Lecture: Debugging NTT Failures",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Problem Set Goals"
}
},
"source": "## META | difficulty 1 | Problem Set Goals\n\nThis notebook checks whether the main NTT bug classes are now distinct in memory.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Multiple-Choice Retrieval"
}
},
"source": "## MANDATORY | difficulty 2 | Multiple-Choice Retrieval\n\nChoose one answer for each:\n\n1. A shuffled but otherwise familiar forward result most strongly suggests:\n A. missing final scaling\n B. wrong BO / NO comparison\n C. wrong modulus\n\n2. An inverse output that looks like a clean multiple of the target most strongly suggests:\n A. missing `n^-1`\n B. wrong wraparound sign\n C. wrong bit-reversal map\n\n3. A local pair that already looks broken in stage 1 most strongly suggests:\n A. wrong zeta\n B. correct CT output\n C. harmless ordering noise\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "quiz",
"title": "Answer Key"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | Answer Key\n\nanswers = {1: \"B\", 2: \"A\", 3: \"A\"}\nprint(answers)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Debug Priority Check"
}
},
"source": "## MANDATORY | difficulty 2 | Debug Priority Check\n\nList the first four checks you would run on a suspicious iNTT output.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "One Good Ordering"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | One Good Ordering\n\ndebug_order = [\n \"check BO / NO assumption\",\n \"check root / zeta schedule\",\n \"check final n^-1 scaling\",\n \"check sign / wraparound conventions\",\n]\nprint(debug_order)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Written Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Written Reflection\n\nIn one paragraph, explain why visible traces are much better debugging tools than only comparing final vectors.\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional Challenge"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional Challenge\n\nmistakes = {\n \"wrong_order\": \"fix the permutation first\",\n \"missing_scale\": \"multiply by n^-1\",\n \"wrong_zeta\": \"rebuild the twiddle schedule\",\n \"wrong_sign\": \"inspect the quotient ring\",\n}\nfor key, value in mistakes.items():\n print(key, \"->\", value)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `studio.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Problems: Debugging NTT Failures",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "orientation",
"title": "Studio Goals"
}
},
"source": "## META | difficulty 1 | Studio Goals\n\nThe last studio compresses the whole course into one debugging mindset.\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "explanation",
"title": "The Whole Course Is A Debugging Ladder"
}
},
"source": "## MANDATORY | difficulty 3 | The Whole Course Is A Debugging Ladder\n\nEvery earlier bundle built one layer of the debugging stack:\n\n- schoolbook grid and wraparound\n- direct transform algebra\n- CT stage schedule\n- GS inverse schedule\n- bit-reversal and scaling\n- Kyber modulus constraints and base multiplication\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 3,
"kind": "demo",
"title": "Print The Whole Debugging Ladder"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 3 | Print The Whole Debugging Ladder\n\nladder = [\n \"Can I see the raw schoolbook product grid?\",\n \"Can I explain the negacyclic wraparound sign?\",\n \"Can I reproduce the direct NTT_psi / INTT_psi round trip?\",\n \"Can I trace CT stages with the right zetas?\",\n \"Can I trace GS stages with the right order and scaling?\",\n \"Can I explain why Kyber v3 does not inherit the full toy psi story unchanged?\",\n]\nfor item in ladder:\n print(\"-\", item)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "Final Debug Checklist"
}
},
"source": "## MANDATORY | difficulty 2 | Final Debug Checklist\n\nKeep this order:\n\n1. ring rule and wraparound\n2. root existence and root choice\n3. stage pairings and zetas\n4. BO vs NO comparison\n5. final `n^-1` scaling\n6. Kyber-specific modulus / base-multiplication assumptions\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "exercise",
"title": "One Last Round Trip"
}
},
"outputs": [],
"source": "# MANDATORY | difficulty 2 | One Last Round Trip\n\nfrom ntt_learning.toy_ntt import fast_intt_psi_gs_trace, fast_ntt_psi_ct_trace\n\nsignal = [3, 1, 4, 1]\nforward_trace = fast_ntt_psi_ct_trace(signal, 17, 2)\ninverse_trace = fast_intt_psi_gs_trace(forward_trace.raw_output, 17, 2)\n\nprint(\"signal:\", signal)\nprint(\"forward BO:\", forward_trace.raw_output)\nprint(\"inverse scaled:\", inverse_trace.scaled_output)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "mandatory",
"difficulty": 2,
"kind": "reflection",
"title": "Final Reflection"
}
},
"source": "## MANDATORY | difficulty 2 | Final Reflection\n\nFinal prompt:\n\n- Which image now anchors your understanding of NTT best: the schoolbook grid, the fold arrows, the CT stage view, the GS stage view, or the bit-reversal map?\n- Why that one?\n"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"pedagogy": {
"role": "facultative",
"difficulty": 4,
"kind": "exploration",
"title": "Optional: Personal Debug Rule"
}
},
"outputs": [],
"source": "# FACULTATIVE | difficulty 4 | Optional: Personal Debug Rule\n\npersonal_rule = \"Never trust a final vector until I have checked the stage trace, the order, and the scaling.\"\nprint(personal_rule)\n"
},
{
"cell_type": "markdown",
"metadata": {
"pedagogy": {
"role": "meta",
"difficulty": 1,
"kind": "handoff",
"title": "Next Notebook"
}
},
"source": "## META | difficulty 1 | Next Notebook\n\nNext notebook: `../../../COURSE_COMPLETE.ipynb`\n"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"ntt_learning": {
"title": "Studio: Debugging NTT Failures",
"contract_version": "0.2",
"sequence": [
"notebooks/START_HERE.ipynb",
"notebooks/COURSE_BLUEPRINT.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lecture.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/lab.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/problems.ipynb",
"notebooks/foundations/01_convolution_to_toy_ntt/studio.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lecture.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/lab.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/problems.ipynb",
"notebooks/foundations/02_negative_wrapped_ntt/studio.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lecture.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/lab.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/problems.ipynb",
"notebooks/butterfly_mechanics/03_fast_forward_ct/studio.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lecture.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/lab.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/problems.ipynb",
"notebooks/butterfly_mechanics/04_fast_inverse_gs/studio.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lecture.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/lab.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/problems.ipynb",
"notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication/studio.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lecture.ipynb",
"notebooks/professional/06_debugging_ntt_failures/lab.ipynb",
"notebooks/professional/06_debugging_ntt_failures/problems.ipynb",
"notebooks/professional/06_debugging_ntt_failures/studio.ipynb",
"notebooks/COURSE_COMPLETE.ipynb"
]
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -3,4 +3,3 @@
from .course import ALL_NOTEBOOKS, ROUTE_NOTEBOOKS, TECHNICAL_NOTEBOOKS
__all__ = ["ALL_NOTEBOOKS", "ROUTE_NOTEBOOKS", "TECHNICAL_NOTEBOOKS"]

View file

@ -9,23 +9,37 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
ROUTE_NOTEBOOKS = [
Path("notebooks/START_HERE.ipynb"),
Path("notebooks/COURSE_BLUEPRINT.ipynb"),
Path("notebooks/COURSE_COMPLETE.ipynb"),
]
FOUNDATION_BUNDLE_DIR = Path("notebooks/foundations/01_convolution_to_toy_ntt")
TECHNICAL_NOTEBOOKS = [
FOUNDATION_BUNDLE_DIR / "lecture.ipynb",
FOUNDATION_BUNDLE_DIR / "lab.ipynb",
FOUNDATION_BUNDLE_DIR / "problems.ipynb",
FOUNDATION_BUNDLE_DIR / "studio.ipynb",
BUNDLE_DIRS = [
Path("notebooks/foundations/01_convolution_to_toy_ntt"),
Path("notebooks/foundations/02_negative_wrapped_ntt"),
Path("notebooks/butterfly_mechanics/03_fast_forward_ct"),
Path("notebooks/butterfly_mechanics/04_fast_inverse_gs"),
Path("notebooks/kyber_mapping/05_kyber_ntt_and_base_multiplication"),
Path("notebooks/professional/06_debugging_ntt_failures"),
]
ALL_NOTEBOOKS = ROUTE_NOTEBOOKS + TECHNICAL_NOTEBOOKS
def bundle_notebooks(bundle_dir: Path) -> list[Path]:
return [
bundle_dir / "lecture.ipynb",
bundle_dir / "lab.ipynb",
bundle_dir / "problems.ipynb",
bundle_dir / "studio.ipynb",
]
TECHNICAL_NOTEBOOKS = [notebook for bundle_dir in BUNDLE_DIRS for notebook in bundle_notebooks(bundle_dir)]
ALL_NOTEBOOKS = ROUTE_NOTEBOOKS[:2] + TECHNICAL_NOTEBOOKS + ROUTE_NOTEBOOKS[2:]
NOTEBOOK_SEQUENCE = [
ROUTE_NOTEBOOKS[0],
ROUTE_NOTEBOOKS[1],
*TECHNICAL_NOTEBOOKS,
ROUTE_NOTEBOOKS[2],
]
REQUIRED_SCRIPT_NAMES = [
@ -42,4 +56,3 @@ CELL_ROLES = {"meta", "mandatory", "facultative"}
ROUTE_ONLY_ROLES = {"meta", "mandatory"}
MANDATORY_DIFFICULTIES = {1, 2, 3}
FACULTATIVE_DIFFICULTIES = {4, 5, 6, 7, 8, 9, 10}

View file

@ -1,4 +1,4 @@
"""Small, inspectable helpers for the first NTT learning module."""
"""Small, inspectable helpers for the NTT learning course."""
from __future__ import annotations
@ -26,6 +26,35 @@ def schoolbook_convolution(
return _apply_mod(result, modulus)
def pairwise_product_grid(left: Sequence[int], right: Sequence[int]) -> list[list[int]]:
"""Return the full schoolbook multiplication grid."""
return [[int(left_value * right_value) for right_value in right] for left_value in left]
def convolution_contributions(left: Sequence[int], right: Sequence[int]) -> list[dict[str, object]]:
"""Return each output index and the contributing schoolbook products."""
raw = schoolbook_convolution(left, right)
rows: list[dict[str, object]] = []
for output_index, total in enumerate(raw):
terms = []
for left_index, left_value in enumerate(left):
right_index = output_index - left_index
if right_index < 0 or right_index >= len(right):
continue
right_value = right[right_index]
terms.append(
{
"left_index": left_index,
"right_index": right_index,
"left_value": int(left_value),
"right_value": int(right_value),
"product": int(left_value * right_value),
}
)
rows.append({"output_index": output_index, "terms": terms, "total": int(total)})
return rows
def negacyclic_reduce(
coefficients: Sequence[int], n: int, modulus: int | None = None
) -> list[int]:
@ -40,6 +69,36 @@ def negacyclic_reduce(
return _apply_mod(reduced, modulus)
def wraparound_contributions(
coefficients: Sequence[int], n: int, negacyclic: bool = True
) -> list[dict[str, object]]:
"""Describe how high-degree terms fold back into degree < n."""
if n <= 0:
raise ValueError("n must be positive")
reduced = [0] * n
rows: list[list[dict[str, int]]] = [[] for _ in range(n)]
for index, coefficient in enumerate(coefficients):
wraps, slot = divmod(index, n)
sign = -1 if negacyclic and wraps % 2 else 1
signed_value = int(sign * coefficient)
reduced[slot] += signed_value
rows[slot].append(
{
"source_index": index,
"wraps": wraps,
"sign": sign,
"value": int(coefficient),
"signed_value": signed_value,
}
)
return [
{"slot": slot, "contributions": rows[slot], "total": int(reduced[slot])}
for slot in range(n)
]
def negacyclic_multiply(
left: Sequence[int], right: Sequence[int], n: int, modulus: int | None = None
) -> list[int]:
@ -55,6 +114,36 @@ def mod_inverse(value: int, modulus: int) -> int:
return pow(value, -1, modulus)
def pointwise_multiply(
left: Sequence[int], right: Sequence[int], modulus: int | None = None
) -> list[int]:
"""Multiply transform-domain entries slot by slot."""
if len(left) != len(right):
raise ValueError("left and right must have the same length")
products = [int(a * b) for a, b in zip(left, right)]
return _apply_mod(products, modulus)
def scale_values(values: Sequence[int], factor: int, modulus: int | None = None) -> list[int]:
"""Multiply all values by a scalar."""
scaled = [int(factor * value) for value in values]
return _apply_mod(scaled, modulus)
def base_multiply_pair(
left: Sequence[int], right: Sequence[int], zeta: int, modulus: int
) -> list[int]:
"""Multiply two degree-1 polynomials in a quadratic factor ring."""
if len(left) != 2 or len(right) != 2:
raise ValueError("base_multiply_pair expects two 2-term coefficient vectors")
a0, a1 = (int(left[0]), int(left[1]))
b0, b1 = (int(right[0]), int(right[1]))
return [
(a0 * b0 + zeta * a1 * b1) % modulus,
(a0 * b1 + a1 * b0) % modulus,
]
def _proper_divisors(order: int) -> list[int]:
return [candidate for candidate in range(1, order) if order % candidate == 0]
@ -76,8 +165,17 @@ def find_primitive_root(order: int, modulus: int) -> int:
raise ValueError(f"no primitive {order}-th root exists modulo {modulus}")
def find_psi(order: int, modulus: int) -> int:
"""Find a primitive ``2 * order``-th root for negative-wrapped NTT."""
full_order = 2 * order
root = find_primitive_root(full_order, modulus)
if pow(root, order, modulus) != (modulus - 1) % modulus:
raise ValueError(f"primitive {full_order}-th root does not satisfy psi^order = -1")
return root
def forward_ntt(values: Sequence[int], modulus: int, omega: int) -> list[int]:
"""Definition-first NTT using the matrix viewpoint."""
"""Definition-first positive-wrapped NTT using the matrix viewpoint."""
n = len(values)
if n == 0:
return []
@ -108,6 +206,50 @@ def inverse_ntt(values: Sequence[int], modulus: int, omega: int) -> list[int]:
return recovered
def ntt_psi_exponent_grid(length: int) -> list[list[int]]:
"""Return the exponent grid used by direct negative-wrapped NTT."""
return [[2 * row * column + row for row in range(length)] for column in range(length)]
def ntt_psi_matrix(length: int, modulus: int, psi: int) -> list[list[int]]:
"""Return the direct negative-wrapped transform matrix."""
return [
[pow(psi, 2 * row * column + row, modulus) for row in range(length)]
for column in range(length)
]
def forward_ntt_psi(values: Sequence[int], modulus: int, psi: int) -> list[int]:
"""Definition-first negative-wrapped NTT using a 2n-th root ``psi``."""
n = len(values)
if n == 0:
return []
spectrum = []
for column in range(n):
total = 0
for row, value in enumerate(values):
total += value * pow(psi, 2 * row * column + row, modulus)
spectrum.append(total % modulus)
return spectrum
def inverse_ntt_psi(values: Sequence[int], modulus: int, psi: int) -> list[int]:
"""Inverse of ``forward_ntt_psi``."""
n = len(values)
if n == 0:
return []
n_inverse = mod_inverse(n, modulus)
recovered = []
for row in range(n):
total = 0
for column, value in enumerate(values):
total += value * pow(psi, -(2 * row * column + row), modulus)
recovered.append((n_inverse * total) % modulus)
return recovered
def ct_butterfly_pair(top: int, bottom: int, zeta: int, modulus: int) -> tuple[int, int]:
"""One Cooley-Tukey style butterfly on a pair."""
twiddled = (zeta * bottom) % modulus
@ -156,6 +298,33 @@ class ButterflyAction:
outputs: tuple[int, int]
@dataclass(frozen=True)
class TransformStage:
"""One visualizable stage in a fast NTT / iNTT trace."""
algorithm: str
stage_index: int
input_values: tuple[int, ...]
output_values: tuple[int, ...]
pairings: tuple[tuple[int, int], ...]
zetas: tuple[int, ...]
note: str
@dataclass(frozen=True)
class TransformTrace:
"""A full fast-transform trace suitable for notebook visualization."""
algorithm: str
modulus: int
root: int
input_values: tuple[int, ...]
stages: tuple[TransformStage, ...]
raw_output: tuple[int, ...]
normal_order_output: tuple[int, ...]
scaled_output: tuple[int, ...] | None = None
def apply_ct_stage(
values: Sequence[int], block_size: int, zetas: int | Iterable[int], modulus: int
) -> tuple[list[int], list[ButterflyAction]]:
@ -217,6 +386,22 @@ def action_rows(actions: Sequence[ButterflyAction]) -> list[dict[str, object]]:
]
def stage_rows(stage: TransformStage) -> list[dict[str, object]]:
"""Return rows describing the pair operations of one trace stage."""
rows = []
for pair, zeta in zip(stage.pairings, stage.zetas):
left, right = pair
rows.append(
{
"pair": pair,
"zeta": zeta,
"inputs": (stage.input_values[left], stage.input_values[right]),
"outputs": (stage.output_values[left], stage.output_values[right]),
}
)
return rows
def bit_reverse(index: int, width: int) -> int:
"""Reverse ``width`` bits from ``index``."""
if width < 0:
@ -229,14 +414,202 @@ def bit_reverse(index: int, width: int) -> int:
return reversed_bits
def bit_reversed_indices(length: int) -> list[int]:
"""Return the bit-reversal permutation for ``length``."""
if length <= 0:
raise ValueError("length must be positive")
if length & (length - 1):
raise ValueError("bit_reversed_indices requires a power-of-two length")
width = length.bit_length() - 1
return [bit_reverse(index, width) for index in range(length)]
def bit_reversed_order(values: Sequence[int]) -> list[int]:
"""Return the array reordered by bit-reversed indices."""
length = len(values)
if length == 0:
return []
if length & (length - 1):
raise ValueError("bit_reversed_order requires a power-of-two length")
permutation = bit_reversed_indices(length)
return [values[index] for index in permutation]
width = length.bit_length() - 1
return [values[bit_reverse(index, width)] for index in range(length)]
def _interleave(left: Sequence[int], right: Sequence[int]) -> list[int]:
result: list[int] = []
for left_value, right_value in zip(left, right):
result.extend([int(left_value), int(right_value)])
return result
def _renumber_stages(stages: Sequence[TransformStage]) -> tuple[TransformStage, ...]:
return tuple(
TransformStage(
algorithm=stage.algorithm,
stage_index=index + 1,
input_values=stage.input_values,
output_values=stage.output_values,
pairings=stage.pairings,
zetas=stage.zetas,
note=stage.note,
)
for index, stage in enumerate(stages)
)
def _merge_child_stages(
left_stages: Sequence[TransformStage], right_stages: Sequence[TransformStage]
) -> list[TransformStage]:
if len(left_stages) != len(right_stages):
raise ValueError("expected the same number of stages on both recursive branches")
merged: list[TransformStage] = []
for left_stage, right_stage in zip(left_stages, right_stages):
merged.append(
TransformStage(
algorithm=left_stage.algorithm,
stage_index=0,
input_values=tuple(_interleave(left_stage.input_values, right_stage.input_values)),
output_values=tuple(_interleave(left_stage.output_values, right_stage.output_values)),
pairings=tuple((2 * a, 2 * b) for a, b in left_stage.pairings)
+ tuple((2 * a + 1, 2 * b + 1) for a, b in right_stage.pairings),
zetas=left_stage.zetas + right_stage.zetas,
note=left_stage.note,
)
)
return merged
def _fast_ntt_psi_ct_recursive(
values: Sequence[int], modulus: int, psi: int
) -> tuple[tuple[int, ...], list[TransformStage]]:
size = len(values)
if size == 1:
return (int(values[0]),), []
even_output, even_stages = _fast_ntt_psi_ct_recursive(values[0::2], modulus, pow(psi, 2, modulus))
odd_output, odd_stages = _fast_ntt_psi_ct_recursive(values[1::2], modulus, pow(psi, 2, modulus))
merged_stages = _merge_child_stages(even_stages, odd_stages)
stage_input = tuple(_interleave(even_output, odd_output))
output = list(stage_input)
pairings = []
zetas = []
for column in range(size // 2):
left = 2 * column
right = 2 * column + 1
zeta = pow(psi, 2 * column + 1, modulus)
output[left], output[right] = ct_butterfly_pair(stage_input[left], stage_input[right], zeta, modulus)
pairings.append((left, right))
zetas.append(zeta)
merged_stages.append(
TransformStage(
algorithm="ct",
stage_index=0,
input_values=stage_input,
output_values=tuple(output),
pairings=tuple(pairings),
zetas=tuple(zetas),
note="Interleave recursive sub-transforms, then apply adjacent CT butterflies.",
)
)
return tuple(output), merged_stages
def fast_ntt_psi_ct_trace(values: Sequence[int], modulus: int, psi: int) -> TransformTrace:
"""Return a stage-by-stage CT fast-NTT trace with BO output and NO comparison."""
if not values:
return TransformTrace("ct", modulus, psi, (), (), (), (), None)
if len(values) & (len(values) - 1):
raise ValueError("fast_ntt_psi_ct_trace requires a power-of-two length")
raw_output, stages = _fast_ntt_psi_ct_recursive(values, modulus, psi)
return TransformTrace(
algorithm="ct",
modulus=modulus,
root=psi,
input_values=tuple(int(value) for value in values),
stages=_renumber_stages(stages),
raw_output=raw_output,
normal_order_output=tuple(bit_reversed_order(raw_output)),
scaled_output=None,
)
def fast_ntt_psi_ct(values: Sequence[int], modulus: int, psi: int) -> list[int]:
"""Compute the BO output of the CT fast negative-wrapped NTT."""
return list(fast_ntt_psi_ct_trace(values, modulus, psi).raw_output)
def _fast_intt_psi_gs_recursive(
values: Sequence[int], modulus: int, psi: int
) -> tuple[tuple[int, ...], list[TransformStage]]:
size = len(values)
if size == 1:
return (int(values[0]),), []
stage_input = tuple(int(value) for value in values)
stage_output = list(stage_input)
pairings = []
zetas = []
for column in range(size // 2):
left = 2 * column
right = 2 * column + 1
zeta = mod_inverse(pow(psi, 2 * column + 1, modulus), modulus)
stage_output[left], stage_output[right] = gs_butterfly_pair(
stage_input[left], stage_input[right], zeta, modulus
)
pairings.append((left, right))
zetas.append(zeta)
even_output, even_stages = _fast_intt_psi_gs_recursive(
stage_output[0::2], modulus, pow(psi, 2, modulus)
)
odd_output, odd_stages = _fast_intt_psi_gs_recursive(
stage_output[1::2], modulus, pow(psi, 2, modulus)
)
return (
tuple(_interleave(even_output, odd_output)),
[
TransformStage(
algorithm="gs",
stage_index=0,
input_values=stage_input,
output_values=tuple(stage_output),
pairings=tuple(pairings),
zetas=tuple(zetas),
note="Apply adjacent GS butterflies, then recurse on even and odd branches.",
),
*_merge_child_stages(even_stages, odd_stages),
],
)
def fast_intt_psi_gs_trace(values: Sequence[int], modulus: int, psi: int) -> TransformTrace:
"""Return a stage-by-stage GS fast-iNTT trace from BO input to NO output."""
if not values:
return TransformTrace("gs", modulus, psi, (), (), (), (), ())
if len(values) & (len(values) - 1):
raise ValueError("fast_intt_psi_gs_trace requires a power-of-two length")
raw_output, stages = _fast_intt_psi_gs_recursive(values, modulus, psi)
n_inverse = mod_inverse(len(values), modulus)
scaled_output = tuple((n_inverse * value) % modulus for value in raw_output)
return TransformTrace(
algorithm="gs",
modulus=modulus,
root=psi,
input_values=tuple(int(value) for value in values),
stages=_renumber_stages(stages),
raw_output=raw_output,
normal_order_output=raw_output,
scaled_output=scaled_output,
)
def fast_intt_psi_gs(values: Sequence[int], modulus: int, psi: int) -> list[int]:
"""Compute the NO output of the GS fast negative-wrapped inverse NTT."""
trace = fast_intt_psi_gs_trace(values, modulus, psi)
return list(trace.scaled_output or ())

329
ntt_learning/visuals.py Normal file
View file

@ -0,0 +1,329 @@
"""Blunt visual helpers for the NTT notebooks."""
from __future__ import annotations
from typing import Sequence
import ipywidgets as widgets
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from IPython.display import clear_output, display
from .toy_ntt import TransformStage, TransformTrace, pairwise_product_grid, wraparound_contributions
def _value_colors(values: Sequence[int]) -> list[str]:
colors = []
for value in values:
if value < 0:
colors.append("#f08a5d")
elif value == 0:
colors.append("#d9d9d9")
else:
colors.append("#7ad3a8")
return colors
def _draw_value_row(ax, values: Sequence[int], y: float, prefix: str) -> None:
colors = _value_colors(values)
for index, (value, color) in enumerate(zip(values, colors)):
ax.text(
index,
y,
f"{prefix}{index}\n{value}",
ha="center",
va="center",
fontsize=10,
family="monospace",
bbox={
"boxstyle": "round,pad=0.35",
"facecolor": color,
"edgecolor": "#222222",
"linewidth": 1.2,
},
)
def plot_convolution_grid(
left: Sequence[int], right: Sequence[int], title: str = "Schoolbook Product Grid"
):
"""Plot the full schoolbook multiplication table and the diagonal sums."""
grid = pairwise_product_grid(left, right)
diagonal_sums = []
for diagonal in range(len(left) + len(right) - 1):
total = 0
for row in range(len(left)):
column = diagonal - row
if 0 <= column < len(right):
total += grid[row][column]
diagonal_sums.append(total)
fig, axes = plt.subplots(2, 1, figsize=(max(7, len(right) * 1.2), 6), height_ratios=[3, 1])
heatmap_ax, sum_ax = axes
heatmap_ax.imshow(grid, cmap="YlGnBu", aspect="auto")
heatmap_ax.set_title(title, fontsize=14, fontweight="bold")
heatmap_ax.set_xlabel("right coefficient index")
heatmap_ax.set_ylabel("left coefficient index")
heatmap_ax.set_xticks(range(len(right)))
heatmap_ax.set_yticks(range(len(left)))
for row, row_values in enumerate(grid):
for column, value in enumerate(row_values):
heatmap_ax.text(column, row, str(value), ha="center", va="center", color="#101010", fontsize=10)
sum_ax.axis("off")
sum_ax.set_title("Diagonal Sums = Convolution Coefficients", fontsize=12, fontweight="bold", pad=8)
for index, value in enumerate(diagonal_sums):
sum_ax.text(
index,
0,
f"y{index}\n{value}",
ha="center",
va="center",
fontsize=10,
family="monospace",
bbox={
"boxstyle": "round,pad=0.35",
"facecolor": "#f4f1de",
"edgecolor": "#222222",
"linewidth": 1.0,
},
)
sum_ax.set_xlim(-0.5, len(diagonal_sums) - 0.5)
sum_ax.set_ylim(-1, 1)
fig.tight_layout()
return fig
def plot_wraparound(
coefficients: Sequence[int],
n: int,
*,
negacyclic: bool = True,
title: str | None = None,
):
"""Plot how the tail wraps back into degree < n."""
rows = wraparound_contributions(coefficients, n=n, negacyclic=negacyclic)
if title is None:
title = "Negacyclic Folding" if negacyclic else "Cyclic Folding"
fig, ax = plt.subplots(figsize=(max(8, len(coefficients) * 1.1), 5.5))
ax.set_title(title, fontsize=14, fontweight="bold")
ax.axis("off")
top_y = 2.4
bottom_y = 0.4
_draw_value_row(ax, coefficients, top_y, "x^")
reduced_values = [row["total"] for row in rows]
_draw_value_row(ax, reduced_values, bottom_y, "slot ")
for slot, row in enumerate(rows):
for contribution in row["contributions"]:
source_index = contribution["source_index"]
color = "#d1495b" if contribution["sign"] < 0 else "#2a9d8f"
label = "-" if contribution["sign"] < 0 else "+"
ax.annotate(
"",
xy=(slot, bottom_y + 0.3),
xytext=(source_index, top_y - 0.25),
arrowprops={"arrowstyle": "->", "color": color, "linewidth": 2.0},
)
mid_x = (slot + source_index) / 2
mid_y = (top_y + bottom_y) / 2 + 0.25
ax.text(
mid_x,
mid_y,
f"{label} wrap {contribution['wraps']}",
ha="center",
va="center",
fontsize=9,
color=color,
family="monospace",
)
ax.set_xlim(-0.8, max(len(coefficients), n) - 0.2)
ax.set_ylim(-0.4, 3.2)
fig.tight_layout()
return fig
def plot_bit_reversal_mapping(length: int, title: str = "Normal Order To Bit-Reversed Order"):
"""Plot the bit-reversal permutation as explicit wires."""
if length <= 0 or length & (length - 1):
raise ValueError("plot_bit_reversal_mapping requires a power-of-two length")
from .toy_ntt import bit_reversed_indices
permutation = bit_reversed_indices(length)
width = length.bit_length() - 1
fig, ax = plt.subplots(figsize=(8, max(4, length * 0.65)))
ax.set_title(title, fontsize=14, fontweight="bold")
ax.axis("off")
for index, target in enumerate(permutation):
ax.text(
0,
-index,
f"{index:>2} | {index:0{width}b}",
ha="center",
va="center",
family="monospace",
bbox={"boxstyle": "round,pad=0.25", "facecolor": "#edf6f9", "edgecolor": "#264653"},
)
ax.text(
4,
-target,
f"{target:>2} | {target:0{width}b}",
ha="center",
va="center",
family="monospace",
bbox={"boxstyle": "round,pad=0.25", "facecolor": "#fff3b0", "edgecolor": "#9c6644"},
)
ax.plot([0.6, 3.4], [-index, -target], color="#7f5539", linewidth=2.2, alpha=0.9)
ax.text(0, 1, "NO", ha="center", va="center", fontsize=12, fontweight="bold")
ax.text(4, 1, "BO", ha="center", va="center", fontsize=12, fontweight="bold")
ax.set_xlim(-1.2, 5.2)
ax.set_ylim(-length + 0.2, 1.8)
fig.tight_layout()
return fig
def plot_stage(stage: TransformStage, title: str | None = None):
"""Plot one explicit butterfly stage with input and output rows."""
if title is None:
title = f"{stage.algorithm.upper()} Stage {stage.stage_index}"
fig, ax = plt.subplots(figsize=(max(8, len(stage.input_values) * 1.35), 5.8))
ax.set_title(title, fontsize=14, fontweight="bold")
ax.axis("off")
input_y = 2.6
output_y = 0.5
_draw_value_row(ax, stage.input_values, input_y, "i")
_draw_value_row(ax, stage.output_values, output_y, "o")
colors = ["#264653", "#2a9d8f", "#e76f51", "#8d99ae", "#c1121f", "#3a86ff"]
for pair_index, ((left, right), zeta) in enumerate(zip(stage.pairings, stage.zetas)):
color = colors[pair_index % len(colors)]
center_x = (left + right) / 2
ax.plot([left, right], [input_y - 0.45, input_y - 0.45], color=color, linewidth=2.5)
ax.plot([left, left], [input_y - 0.45, output_y + 0.55], color=color, linewidth=1.5, alpha=0.85)
ax.plot([right, right], [input_y - 0.45, output_y + 0.55], color=color, linewidth=1.5, alpha=0.85)
ax.text(
center_x,
1.55,
f"pair {left}-{right}\nzeta={zeta}",
ha="center",
va="center",
fontsize=10,
family="monospace",
bbox={
"boxstyle": "round,pad=0.35",
"facecolor": "#ffffff",
"edgecolor": color,
"linewidth": 1.4,
},
)
ax.text(
len(stage.input_values) / 2 - 0.5,
-0.05,
stage.note,
ha="center",
va="center",
fontsize=10,
color="#333333",
)
ax.set_xlim(-0.8, len(stage.input_values) - 0.2)
ax.set_ylim(-0.5, 3.3)
fig.tight_layout()
return fig
def plot_trace_overview(trace: TransformTrace, title: str | None = None):
"""Plot every stage output as a column of values."""
if title is None:
title = f"{trace.algorithm.upper()} Trace Overview"
columns = [trace.input_values] + [stage.output_values for stage in trace.stages]
fig, ax = plt.subplots(figsize=(max(9, len(columns) * 2.0), max(4.5, len(trace.input_values) * 0.7)))
ax.set_title(title, fontsize=14, fontweight="bold")
ax.axis("off")
for column_index, values in enumerate(columns):
x = column_index * 2.0
for row_index, value in enumerate(values):
ax.text(
x,
-row_index,
str(value),
ha="center",
va="center",
fontsize=10,
family="monospace",
bbox={
"boxstyle": "round,pad=0.25",
"facecolor": _value_colors([value])[0],
"edgecolor": "#222222",
"linewidth": 1.0,
},
)
if column_index == 0:
label = "input"
else:
label = f"stage {column_index}"
ax.text(x, 1, label, ha="center", va="center", fontsize=11, fontweight="bold")
ax.set_xlim(-1.0, (len(columns) - 1) * 2.0 + 1.0)
ax.set_ylim(-len(trace.input_values) + 0.2, 1.8)
fig.tight_layout()
return fig
def interactive_trace(trace: TransformTrace, title: str | None = None):
"""Return a slider-based stage explorer for a transform trace."""
if title is None:
title = f"{trace.algorithm.upper()} Stage Explorer"
slider = widgets.IntSlider(
value=1,
min=1,
max=max(1, len(trace.stages)),
step=1,
description="Stage",
continuous_update=False,
)
output = widgets.Output()
def render(stage_index: int) -> None:
with output:
clear_output(wait=True)
stage = trace.stages[stage_index - 1]
fig = plot_stage(stage, title=f"{title} | Stage {stage_index}")
display(fig)
plt.close(fig)
rows = []
for pair, zeta in zip(stage.pairings, stage.zetas):
left, right = pair
rows.append(
f"pair {pair}: inputs=({stage.input_values[left]}, {stage.input_values[right]}) "
f"-> outputs=({stage.output_values[left]}, {stage.output_values[right]}) | zeta={zeta}"
)
print("\n".join(rows))
slider.observe(lambda change: render(change["new"]), names="value")
render(slider.value)
widget = widgets.VBox([widgets.HTML(f"<h4>{title}</h4>"), slider, output])
return widget
def show_trace(trace: TransformTrace, title: str | None = None):
"""Display the interactive trace widget immediately."""
widget = interactive_trace(trace, title=title)
display(widget)
return widget

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import contextlib
import io
import json
import os
import sys
import unittest
from pathlib import Path
@ -12,6 +13,12 @@ from ntt_learning.course import REPO_ROOT, TECHNICAL_NOTEBOOKS
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
MPLCONFIGDIR = REPO_ROOT / ".cache" / "matplotlib"
MPLCONFIGDIR.mkdir(parents=True, exist_ok=True)
os.environ.setdefault("MPLCONFIGDIR", str(MPLCONFIGDIR))
import matplotlib.pyplot as plt
def read_notebook(relative_path: Path) -> dict[str, object]:
return json.loads((REPO_ROOT / relative_path).read_text(encoding="utf-8"))
@ -46,8 +53,8 @@ class NotebookExecutionTests(unittest.TestCase):
mode="exec",
)
exec(code_object, namespace, namespace)
plt.close("all")
if __name__ == "__main__":
unittest.main()

View file

@ -4,12 +4,21 @@ import unittest
from ntt_learning.toy_ntt import (
apply_ct_stage,
base_multiply_pair,
bit_reverse,
bit_reversed_indices,
bit_reversed_order,
ct_butterfly_pair,
fast_intt_psi_gs,
fast_intt_psi_gs_trace,
fast_ntt_psi_ct,
fast_ntt_psi_ct_trace,
find_psi,
find_primitive_root,
forward_ntt,
forward_ntt_psi,
inverse_ntt,
inverse_ntt_psi,
negacyclic_multiply,
negacyclic_reduce,
schoolbook_convolution,
@ -53,6 +62,37 @@ class ToyNttTests(unittest.TestCase):
def test_bit_reversed_order(self) -> None:
self.assertEqual(bit_reversed_order(list(range(8))), [0, 4, 2, 6, 1, 5, 3, 7])
def test_bit_reversed_indices(self) -> None:
self.assertEqual(bit_reversed_indices(8), [0, 4, 2, 6, 1, 5, 3, 7])
def test_find_psi(self) -> None:
self.assertEqual(find_psi(order=4, modulus=17), 2)
def test_forward_inverse_ntt_psi_round_trip(self) -> None:
signal = [6, 0, 5, 2]
psi = find_psi(order=4, modulus=17)
spectrum = forward_ntt_psi(signal, modulus=17, psi=psi)
self.assertEqual(inverse_ntt_psi(spectrum, modulus=17, psi=psi), signal)
def test_fast_ct_trace_matches_paper_example(self) -> None:
trace = fast_ntt_psi_ct_trace([1, 2, 3, 4], modulus=7681, psi=1925)
self.assertEqual(list(trace.raw_output), [1467, 3471, 2807, 7621])
self.assertEqual(list(trace.normal_order_output), [1467, 2807, 3471, 7621])
def test_fast_gs_trace_matches_paper_example(self) -> None:
trace = fast_intt_psi_gs_trace([1467, 3471, 2807, 7621], modulus=7681, psi=1925)
self.assertEqual(list(trace.raw_output), [4, 8, 12, 16])
self.assertEqual(list(trace.scaled_output or ()), [1, 2, 3, 4])
def test_fast_ct_and_gs_round_trip(self) -> None:
signal = [3, 1, 4, 1]
psi = find_psi(order=4, modulus=17)
bo_spectrum = fast_ntt_psi_ct(signal, modulus=17, psi=psi)
self.assertEqual(fast_intt_psi_gs(bo_spectrum, modulus=17, psi=psi), signal)
def test_base_multiply_pair(self) -> None:
self.assertEqual(base_multiply_pair([1, 2], [3, 4], zeta=5, modulus=17), [9, 10])
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load diff