Three more animations: the stack machine, a dict lookup and a cycle - #67
Merged
Conversation
a03 runs one call to `def area(w): return w * 2 + 1` one instruction at a time with the value stack drawn beside the listing. A disassembly listing tells you what each instruction is called and nothing about what it does to the pile of values underneath, and the pile is the part that is hard. The instructions and the depths come from `pyxray.stack.walk` on 3.15.0rc1, which is why the listing says LOAD_FAST_BORROW and not LOAD_FAST. a04 is a dict lookup that collides. Since 3.6 a dict has been a small array of slot numbers and a separate list of entries in insertion order, and both of the questions people ask about dicts follow from that split. The eight slot array on screen was read out of a live interpreter through ctypes rather than derived from the probing rule, and a test does the same read and fails if CPython ever lays a small dict out differently. a05 is two objects that hold each other. Take the names away and both counts drop to one, which is not zero, so nothing frees them and nothing can reach them either. Then the collector's trick, which is smaller than people expect: copy each count, subtract one per reference found inside the group, and a copy that reaches zero was only ever being kept alive from inside. The tally in tear_down had never run once. The guard read `config.upto_animation_number >= 0`, but manim's default for that is math.inf rather than -1, so every render took the early return, including the real ones. It was found because a03 played seven of its eight beats and rendered without complaint. There is now a test that pins the default down and says why. Frame drew its value stack hanging from the rule, so it grew downward as values were pushed, which contradicts the one rule the visual system states without exception. It now sits on the floor of the frame and grows up, an empty stack is the floor with nothing on it, and both regions are captioned. arrow can bend, which is the one case a straight line cannot draw: two objects pointing at each other come out as one line drawn twice. graph works out which side of a box an edge leaves from instead of always leaving from the bottom, and passes the bend on. DictTable writes the slot numbers outside the array, because a slot number is a position and not a thing CPython stores, and a picture whose cells have to be counted gets read wrong. The five animations between them now draw all nine primitives and all six named objects, and there is a test that says so.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five animations now exist, which is the M1 item, and the set between them draws all nine primitives and all six named objects.
a03, the stack machine
One call to
def area(w): return w * 2 + 1, run one instruction at a time, with the value stack on screen beside the listing. A disassembly listing tells you what each instruction is called and nothing about what it does to the pile of values underneath, and the pile is the part that is actually hard for a beginner. The function was chosen for its stack profile rather than for what it computes: the depth goes 0, 1, 2, 1, 2, 1, and those two climbs and two drops are the whole idea.The instructions and the depths are from
pyxray.stack.walkon 3.15.0rc1. The listing saysLOAD_FAST_BORROWrather thanLOAD_FASTbecause that is what the compiler emits here, and a reader who disassembles the function themselves should see what we showed them.a04, how a dict finds a key
{1: "one", 5: "five", 9: "nine"}and thend[9], which collides. Since 3.6 a dict has not been one table of key and value pairs: there is a small array of slot numbers and a separate list of entries in the order they went in, and the two questions people actually ask about dicts both follow from that split. Key 9 lands on slot 1, finds key 1 there, probes again, and finds it in slot 6 on the second look.Integer keys, because their hash is the value and is not randomized, so the picture is the same on every run. The index array on screen is
[-1, 0, -1, -1, -1, 1, 2, -1]and it was not derived from the probing rule, it was read out of a live interpreter throughctypesat the address of the dict.test_xraymanim_facts.pydoes the same read and fails if CPython ever lays a small dict out differently.a05, a cycle, and what frees it
Two objects that hold each other. With both names bound each count is 2, one from the name and one from the other object. After
del a, beach is 1, and one is not zero, so nothing frees them and nothing can reach them either. Then the collector, which is smaller than people expect: it copies each count, subtracts one for every reference it finds from one tracked object to another, and a copy that reaches zero was only ever being kept alive from inside the group.The counts are read out of the header with
ctypesrather than throughsys.getrefcount, because passing an object to a function is itself a reference and this animation is about counting references exactly.gc.collect()returning 2 is checked too.The tally in tear_down had never run
This is the thing worth reading twice.
Explainer.tear_downchecks that a scene played the beats its storyboard planned, and that check is the reason the storyboards are worth having at all. The guard that switches it off for a partial render readconfig.from_animation_number > 0 or config.upto_animation_number >= 0. Manim's default for the upper bound ismath.inf, not-1, andmath.inf >= 0is true, so every render took the early return. It had never once run, including in CI.It turned up because a03 played seven of its eight beats and rendered without a word. Proved by rendering a deliberately broken scene, which exited 0 before the fix and raises
AssertionError: a03-the-stack-machine played 1 beat(s) but its storyboard has 8after it. There is now a test that asserts the default is infinity and says in its docstring why that line exists.Fixes to the shared library
Framedrew its value stack hanging from the rule at the top, so it grew downward as values were pushed. Every stack in this project grows upward without exception, and a reader who sees that broken once reads every later picture wrong. The stack now sits on the floor of the frame, an empty stack is the floor with nothing on it rather than a gap, and the locals and the stack are both captioned.arrowtakes abend, which is for the one case a straight line cannot draw: two objects pointing at each other come out as one line drawn twice. Bent by the same angle the pair comes out as the lens shape everybody draws a cycle as, because the two arrows have their ends swapped.graphworks out which side of a box an edge leaves from, instead of always leaving from the bottom, which is right for a tree and wrong for everything else. Positions are still given by hand.DictTablewrites the slot numbers outside the index array. A slot number is a position, in the same way a list index is, and CPython does not store it anywhere, so it is written beside the array and not in it. Without them a reader has to count cells to find slot 6, and a picture that has to be counted is a picture that gets read wrong. The gap between the two arrays is wider too, because they are two separate pieces of memory and every arrow in a04 is drawn across it.VISUAL-SYSTEM.mdis amended for all three, which is the rule the library enforces on itself.Checks
just checkis green: 970 tests, 352 citations, 12 lessons, 12 notebooks, 2 blueprints, 96 diagrams, 5 animations. The manim half of the suite is 71 tests and passes under--extra anim. The three GIFs are 1.7 MB, 1.2 MB and 1.1 MB, all well under the 6 MB limit.Part of #20.