Files
manim/fractal_dimension.py
2017-01-20 09:26:14 -08:00

2409 lines
71 KiB
Python

#!/usr/bin/env python
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from scene import Scene
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from eoc.graph_scene import GraphScene
def break_up(mobject, factor = 1.3):
mobject.scale_in_place(factor)
for submob in mobject:
submob.scale_in_place(1./factor)
return mobject
class Britain(SVGMobject):
CONFIG = {
"file_name" : "Britain.svg",
"stroke_width" : 0,
"fill_color" : BLUE_D,
"fill_opacity" : 1,
"propogate_style_to_family" : True,
"height" : 5,
"mark_paths_closed" : True,
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.points = self[0].points
self.submobjects = []
self.scale_to_fit_height(self.height)
self.center()
class Norway(Britain):
CONFIG = {
"file_name" : "Norway",
"mark_paths_closed" : False
}
class KochTest(Scene):
def construct(self):
koch = KochCurve(order = 5, stroke_width = 2)
self.play(ShowCreation(koch, run_time = 3))
self.play(
koch.scale, 3, koch.get_left(),
koch.set_stroke, None, 4
)
self.dither()
class SierpinskiTest(Scene):
def construct(self):
sierp = Sierpinski(
order = 5,
)
self.play(FadeIn(
sierp,
run_time = 5,
submobject_mode = "lagged_start",
))
self.dither()
# self.play(sierp.scale, 2, sierp.get_top())
# self.dither(3)
class FractalCreation(Scene):
CONFIG = {
"fractal_class" : PentagonalFractal,
"max_order" : 5,
"path_arc" : np.pi/6,
"submobject_mode" : "lagged_start"
}
def construct(self):
fractal = self.fractal_class(order = 0)
self.play(FadeIn(fractal))
for order in range(1, self.max_order+1):
new_fractal = self.fractal_class(order = order)
self.play(Transform(
fractal, new_fractal,
path_arc = self.path_arc,
submobject_mode = self.submobject_mode,
run_time = 2
))
self.dither()
self.dither()
###################################
class ZoomInOnFractal(PiCreatureScene):
CONFIG = {
"fractal_order" : 6,
"num_zooms" : 5,
"fractal_class" : DiamondFractal,
"index_to_replace" : 0,
}
def construct(self):
morty = self.pi_creature
fractal = self.fractal_class(order = self.fractal_order)
fractal.show()
fractal = self.introduce_fractal()
self.change_mode("thinking")
self.blink()
self.zoom_in(fractal)
def introduce_fractal(self):
fractal = self.fractal_class(order = 0)
self.play(FadeIn(fractal))
for order in range(1, self.fractal_order+1):
new_fractal = self.fractal_class(order = order)
self.play(
Transform(fractal, new_fractal, run_time = 2),
self.pi_creature.change_mode, "hooray"
)
return fractal
def zoom_in(self, fractal):
grower = fractal[self.index_to_replace]
grower_target = fractal.copy()
for x in range(self.num_zooms):
self.tweak_fractal_subpart(grower_target)
grower_family = grower.family_members_with_points()
everything = VGroup(*[
submob
for submob in fractal.family_members_with_points()
if not submob.is_off_screen()
if submob not in grower_family
])
everything.generate_target()
everything.target.shift(
grower_target.get_center()-grower.get_center()
)
everything.target.scale(
grower_target.get_height()/grower.get_height()
)
self.play(
Transform(grower, grower_target),
MoveToTarget(everything),
self.pi_creature.change_mode, "thinking",
run_time = 2
)
self.dither()
grower_target = grower.copy()
grower = grower[self.index_to_replace]
def tweak_fractal_subpart(self, subpart):
subpart.rotate_in_place(np.pi/4)
class WhatAreFractals(TeacherStudentsScene):
def construct(self):
self.student_says(
"But what \\emph{is} a fractal?",
student_index = 2,
width = 6
)
self.change_student_modes("thinking", "pondering", None)
self.dither()
name = TextMobject("Benoit Mandelbrot")
name.to_corner(UP+LEFT)
# picture = Rectangle(height = 4, width = 3)
picture = ImageMobject("Mandelbrot")
picture.scale_to_fit_height(4)
picture.next_to(name, DOWN)
self.play(
Write(name, run_time = 2),
FadeIn(picture),
*[
ApplyMethod(pi.look_at, name)
for pi in self.get_everyone()
]
)
self.dither(2)
question = TextMobject("Aren't they", "self-similar", "shapes?")
question.highlight_by_tex("self-similar", YELLOW)
self.student_says(question)
self.play(self.get_teacher().change_mode, "happy")
self.dither(2)
class IntroduceVonKochCurve(Scene):
CONFIG = {
"order" : 5,
"stroke_width" : 3,
}
def construct(self):
snowflake = self.get_snowflake()
name = TextMobject("Von Koch Snowflake")
name.to_edge(UP)
self.play(ShowCreation(snowflake, run_time = 3))
self.play(Write(name, run_time = 2))
curve = self.isolate_one_curve(snowflake)
self.dither()
self.zoom_in_on(curve)
def get_snowflake(self):
triangle = RegularPolygon(n = 3, start_angle = np.pi/2)
triangle.scale_to_fit_height(4)
curves = VGroup(*[
KochCurve(
order = self.order,
stroke_width = self.stroke_width
)
for x in range(3)
])
for index, curve in enumerate(curves):
width = curve.get_width()
curve.move_to(
(np.sqrt(3)/6)*width*UP, DOWN
)
curve.rotate(-index*2*np.pi/3)
curves.gradient_highlight(BLUE, WHITE, BLUE)
return curves
def isolate_one_curve(self, snowflake):
self.play(*[
ApplyMethod(curve.shift, curve.get_center()/2)
for curve in snowflake
])
self.dither()
self.play(
snowflake.scale, 2.1,
snowflake.next_to, UP, DOWN
)
self.remove(*snowflake[1:])
return snowflake[0]
def zoom_in_on(self, curve):
larger_curve = KochCurve(order = self.order+1)
larger_curve.replace(curve)
larger_curve.scale(3, about_point = curve.get_corner(DOWN+LEFT))
larger_curve.gradient_highlight(
curve[0].get_color(),
curve[-1].get_color(),
)
self.play(Transform(curve, larger_curve, run_time = 2))
n_parts = len(curve.split())
sub_portion = VGroup(*curve[:n_parts/4])
self.play(
sub_portion.highlight, YELLOW,
rate_func = there_and_back
)
self.dither()
class IntroduceSierpinskiTriangle(PiCreatureScene):
CONFIG = {
"order" : 7,
}
def construct(self):
sierp = Sierpinski(order = self.order)
sierp.save_state()
self.play(FadeIn(
sierp,
run_time = 2,
submobject_mode = "lagged_start"
))
self.dither()
self.play(
self.pi_creature.change_mode, "pondering",
*[
ApplyMethod(submob.shift, submob.get_center())
for submob in sierp
]
)
self.dither()
for submob in sierp:
self.play(sierp.shift, -submob.get_center())
self.dither()
self.play(sierp.restore)
self.change_mode("happy")
self.dither()
class SelfSimilarFractalsAsSubset(Scene):
CONFIG = {
"fractal_width" : 1.5
}
def construct(self):
self.add_self_similar_fractals()
self.add_general_fractals()
def add_self_similar_fractals(self):
fractals = VGroup(
DiamondFractal(order = 5),
KochSnowFlake(order = 3),
Sierpinski(order = 5),
)
for submob in fractals:
submob.scale_to_fit_width(self.fractal_width)
fractals.arrange_submobjects(RIGHT)
fractals[-1].next_to(VGroup(*fractals[:-1]), DOWN)
title = TextMobject("Self-similar fractals")
title.next_to(fractals, UP)
small_rect = Rectangle()
small_rect.replace(VGroup(fractals, title), stretch = True)
small_rect.scale_in_place(1.2)
self.small_rect = small_rect
group = VGroup(fractals, title, small_rect)
group.to_corner(UP+LEFT, buff = 2*MED_BUFF)
self.play(
Write(title),
ShowCreation(fractals),
run_time = 3
)
self.play(ShowCreation(small_rect))
self.dither()
def add_general_fractals(self):
big_rectangle = Rectangle(
width = 2*SPACE_WIDTH - 2*MED_BUFF,
height = 2*SPACE_HEIGHT - 2*MED_BUFF,
)
title = TextMobject("Fractals")
title.scale(1.5)
title.next_to(ORIGIN, RIGHT, buff = LARGE_BUFF)
title.to_edge(UP, buff = 2*MED_BUFF)
britain = Britain(
fill_opacity = 0,
stroke_width = 2,
stroke_color = WHITE,
)
britain.next_to(self.small_rect, RIGHT)
britain.shift(2*DOWN)
randy = Randolph().flip().scale(1.4)
randy.next_to(britain, buff = SMALL_BUFF)
randy.generate_target()
randy.target.change_mode("pleading")
fractalify(randy.target, order = 2)
self.play(
ShowCreation(big_rectangle),
Write(title),
)
self.play(ShowCreation(britain), run_time = 5)
self.play(
britain.set_fill, BLUE, 1,
britain.set_stroke, None, 0,
run_time = 2
)
self.play(FadeIn(randy))
self.play(MoveToTarget(randy, run_time = 2))
self.dither(2)
class ConstrastSmoothAndFractal(Scene):
CONFIG = {
"britain_zoom_point_proportion" : 0.45,
"scale_factor" : 50,
"fractalification_order" : 2,
"fractal_dimension" : 1.21,
}
def construct(self):
v_line = Line(UP, DOWN).scale(SPACE_HEIGHT)
smooth = TextMobject("Smooth")
smooth.shift(SPACE_WIDTH*LEFT/2)
fractal = TextMobject("Fractal")
fractal.shift(SPACE_WIDTH*RIGHT/2)
VGroup(smooth, fractal).to_edge(UP)
background_rectangle = Rectangle(
height = 2*SPACE_HEIGHT,
width = SPACE_WIDTH,
)
background_rectangle.to_edge(RIGHT, buff = 0)
background_rectangle.set_fill(BLACK, 1)
background_rectangle.set_stroke(width = 0)
self.add(v_line, background_rectangle, smooth, fractal)
britain = Britain(
fill_opacity = 0,
stroke_width = 2,
stroke_color = WHITE,
)[0]
anchors = britain.get_anchors()
smooth_britain = VMobject()
smooth_britain.set_points_smoothly(anchors[::10])
smooth_britain.center().shift(SPACE_WIDTH*LEFT/2)
index = np.argmax(smooth_britain.get_anchors()[:,0])
smooth_britain.zoom_point = smooth_britain.point_from_proportion(
self.britain_zoom_point_proportion
)
britain.shift(SPACE_WIDTH*RIGHT/2)
britain.zoom_point = britain.point_from_proportion(
self.britain_zoom_point_proportion
)
fractalify(
britain,
order = self.fractalification_order,
dimension = self.fractal_dimension,
)
britains = VGroup(britain, smooth_britain)
self.play(*[
ShowCreation(mob, run_time = 3)
for mob in britains
])
self.play(
britains.set_fill, BLUE, 1,
britains.set_stroke, None, 0,
)
self.dither()
self.play(
ApplyMethod(
smooth_britain.scale,
self.scale_factor,
smooth_britain.zoom_point
),
Animation(v_line),
Animation(background_rectangle),
ApplyMethod(
britain.scale,
self.scale_factor,
britain.zoom_point
),
Animation(smooth),
Animation(fractal),
run_time = 10,
)
self.dither(2)
class ShowIdealizations(Scene):
def construct(self):
arrow = DoubleArrow(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
arrow.shift(DOWN)
left_words = TextMobject("Idealization \\\\ as smooth")
middle_words = TextMobject("Nature")
right_words = TextMobject("""
Idealization
as perfectly
self-similar
""")
for words in left_words, middle_words, right_words:
words.scale(0.8)
words.next_to(arrow, DOWN)
left_words.to_edge(LEFT)
right_words.to_edge(RIGHT)
self.add(arrow, left_words, middle_words, right_words)
britain = Britain()[0]
britain.scale_to_fit_height(4)
britain.next_to(arrow, UP)
anchors = britain.get_anchors()
smooth_britain = VMobject()
smooth_britain.set_points_smoothly(anchors[::10])
smooth_britain.set_stroke(width = 0)
smooth_britain.set_fill(BLUE_D, opacity = 1)
smooth_britain.next_to(arrow, UP)
smooth_britain.to_edge(LEFT)
koch_snowflake = KochSnowFlake(order = 5, monochromatic = True)
koch_snowflake.set_stroke(width = 0)
koch_snowflake.set_fill(BLUE_D, opacity = 1)
koch_snowflake.scale_to_fit_height(3)
koch_snowflake.rotate(2*np.pi/3)
koch_snowflake.next_to(arrow, UP)
koch_snowflake.to_edge(RIGHT)
VGroup(smooth_britain, britain, koch_snowflake).gradient_highlight(
BLUE_B, BLUE_D
)
self.play(FadeIn(britain))
self.dither()
self.play(Transform(britain.copy(), smooth_britain))
self.dither()
self.play(Transform(britain.copy(), koch_snowflake))
self.dither()
self.dither(2)
class SayFractalDimension(TeacherStudentsScene):
def construct(self):
self.teacher_says("Fractal dimension")
self.change_student_modes("confused", "hesitant", "pondering")
self.dither(3)
class ExamplesOfDimension(Scene):
def construct(self):
labels = VGroup(*[
TextMobject("%s-dimensional"%s)
for s in "1.585", "1.262", "1.21"
])
fractals = VGroup(*[
Sierpinski(order = 7),
KochSnowFlake(order = 5),
Britain(stroke_width = 2, fill_opacity = 0)
])
for fractal, vect in zip(fractals, [LEFT, ORIGIN, RIGHT]):
fractal.to_edge(vect)
fractals[2].shift(0.5*UP)
fractals[1].shift(0.5*RIGHT)
for fractal, label, vect in zip(fractals, labels, [DOWN, UP, DOWN]):
label.next_to(fractal, vect)
label.shift_onto_screen()
self.play(
ShowCreation(fractal),
Write(label),
run_time = 3
)
self.dither()
self.dither()
class DimensionForNaturalNumbers(Scene):
def construct(self):
labels = VGroup(*[
TextMobject("%d-dimensional"%d)
for d in 1, 2, 3
])
for label, vect in zip(labels, [LEFT, ORIGIN, RIGHT]):
label.to_edge(vect)
labels.shift(2*DOWN)
line = Line(DOWN+LEFT, 3*UP+RIGHT, color = BLUE)
line.next_to(labels[0], UP)
self.play(
Write(labels[0]),
ShowCreation(line)
)
self.dither()
for label in labels[1:]:
self.play(Write(label))
self.dither()
class Show2DPlanein3D(Scene):
def construct(self):
pass
class ShowCubeIn3D(Scene):
def construct(self):
pass
class OfCourseItsMadeUp(TeacherStudentsScene):
def construct(self):
self.teacher_says("""
Fractal dimension
\\emph{is} a made up concept...
""")
self.change_student_modes(*["hesitant"]*3)
self.dither(2)
self.teacher_says(
"""But it's useful!""",
target_mode = "hooray"
)
class FourSelfSimilarShapes(Scene):
CONFIG = {
"shape_width" : 2,
"sierpinski_order" : 6,
}
def construct(self):
titles = self.get_titles()
shapes = self.get_shapes(titles)
self.introduce_shapes(titles, shapes)
self.show_self_similarity(shapes)
self.mention_measurements()
def get_titles(self):
titles = VGroup(*map(TextMobject, [
"Line", "Square", "Cube", "Sierpinski"
]))
for title, x in zip(titles, np.linspace(-0.75, 0.75, 4)):
title.shift(x*SPACE_WIDTH*RIGHT)
titles.to_edge(UP)
return titles
def get_shapes(self, titles):
line = VGroup(
Line(LEFT, ORIGIN),
Line(ORIGIN, RIGHT)
)
line.highlight(BLUE_C)
square = VGroup(*[
Square().next_to(ORIGIN, vect, buff = 0)
for vect in compass_directions(start_vect = DOWN+LEFT)
])
square.set_stroke(width = 0)
square.set_fill(BLUE, 0.7)
cube = TextMobject("TODO")
cube.set_fill(opacity = 0)
sierpinski = Sierpinski(order = self.sierpinski_order)
shapes = VGroup(line, square, cube, sierpinski)
for shape, title in zip(shapes, titles):
shape.scale_to_fit_width(self.shape_width)
shape.next_to(title, DOWN, buff = MED_BUFF)
line.shift(DOWN)
return shapes
def introduce_shapes(self, titles, shapes):
line, square, cube, sierpinski = shapes
brace = Brace(VGroup(*shapes[:3]), DOWN)
brace_text = brace.get_text("Not fractals")
self.play(ShowCreation(line))
self.play(GrowFromCenter(square))
self.play(FadeIn(cube))
self.play(ShowCreation(sierpinski))
self.dither()
self.play(
GrowFromCenter(brace),
FadeIn(brace_text)
)
self.dither()
self.play(*map(FadeOut, [brace, brace_text]))
self.dither()
for title in titles:
self.play(Write(title, run_time = 1))
self.dither(2)
def show_self_similarity(self, shapes):
shapes_copy = shapes.copy()
self.shapes_copy = shapes_copy
line, square, cube, sierpinski = shapes_copy
self.play(line.shift, 3*DOWN)
self.play(ApplyFunction(break_up, line))
self.dither()
brace = Brace(line[0], DOWN)
brace_text = brace.get_text("1/2")
self.play(
GrowFromCenter(brace),
Write(brace_text)
)
brace.add(brace_text)
self.dither()
self.play(square.next_to, square, DOWN, LARGE_BUFF)
self.play(ApplyFunction(break_up, square))
subsquare = square[0]
subsquare.save_state()
self.play(subsquare.replace, shapes[1])
self.dither()
self.play(subsquare.restore)
self.play(brace.next_to, subsquare, DOWN)
self.dither()
self.dither(5)#Handle cube
self.play(sierpinski.next_to, sierpinski, DOWN, LARGE_BUFF)
self.play(ApplyFunction(break_up, sierpinski))
self.dither()
self.play(brace.next_to, sierpinski[0], DOWN)
self.dither(2)
self.play(FadeOut(brace))
def mention_measurements(self):
line, square, cube, sierpinski = self.shapes_copy
labels = map(TextMobject, [
"$1/2$ length",
"$1/4$ area",
"$1/8$ volume",
"You'll see...",
])
for label, shape in zip(labels, self.shapes_copy):
label.next_to(shape, DOWN)
label.to_edge(DOWN, buff = 2*MED_BUFF)
if label is labels[-1]:
label.shift(0.1*UP) #Dumb
self.play(
Write(label, run_time = 1),
shape[0].highlight, YELLOW
)
self.dither()
class BreakUpCubeIn3D(Scene):
def construct(self):
pass
class BrokenUpCubeIn3D(Scene):
def construct(self):
pass
class GeneralWordForMeasurement(Scene):
def construct(self):
measure = TextMobject("``Measure''")
measure.to_edge(UP)
mass = TextMobject("Mass")
mass.move_to(measure)
words = VGroup(*map(TextMobject, [
"Length", "Area", "Volume"
]))
words.arrange_submobjects(RIGHT, buff = 2*LARGE_BUFF)
words.next_to(measure, DOWN, buff = 2*LARGE_BUFF)
colors = color_gradient([BLUE_B, BLUE_D], len(words))
for word, color in zip(words, colors):
word.highlight(color)
lines = VGroup(*[
Line(
measure.get_bottom(), word.get_top(),
color = word.get_color(),
buff = MED_BUFF
)
for word in words
])
for word in words:
self.play(FadeIn(word))
self.play(ShowCreation(lines, run_time = 2))
self.dither()
self.play(Write(measure))
self.dither(2)
self.play(Transform(measure, mass))
self.dither(2)
class ImagineShapesAsMetal(FourSelfSimilarShapes):
def construct(self):
titles = VGroup(*map(VGroup, self.get_titles()))
shapes = self.get_shapes(titles)
shapes.shift(DOWN)
descriptions = VGroup(*[
TextMobject(*words, arg_separator = "\\\\")
for shape, words in zip(shapes, [
["Thin", "wire"],
["Flat", "sheet"],
["Solid", "cube"],
["Sierpinski", "mesh"]
])
])
for title, description in zip(titles, descriptions):
description.move_to(title, UP)
title.target = description
self.add(titles, shapes)
for shape in shapes:
shape.generate_target()
shape.target.highlight(LIGHT_GREY)
shapes[-1].target.gradient_highlight(GREY, WHITE)
for shape, title in zip(shapes, titles):
self.play(
MoveToTarget(title),
MoveToTarget(shape)
)
self.dither()
self.dither()
for shape in shapes:
self.play(
shape.scale, 0.5, shape.get_top(),
run_time = 3,
rate_func = there_and_back
)
self.dither()
class ScaledLineMass(Scene):
CONFIG = {
"title" : "Line",
"mass_scaling_factor" : "\\frac{1}{2}",
"shape_width" : 2,
"break_up_factor" : 1.3,
"vert_distance" : 2,
"brace_direction" : DOWN,
"shape_to_shape_buff" : 2*LARGE_BUFF,
}
def construct(self):
title = TextMobject(self.title)
title.to_edge(UP)
scaling_factor_label = TextMobject(
"Scaling factor:", "$\\frac{1}{2}$"
)
scaling_factor_label[1].highlight(YELLOW)
scaling_factor_label.to_edge(LEFT).shift(UP)
mass_scaling_label = TextMobject(
"Mass scaling factor:", "$%s$"%self.mass_scaling_factor
)
mass_scaling_label[1].highlight(GREEN)
mass_scaling_label.next_to(
scaling_factor_label, DOWN,
aligned_edge = LEFT,
buff = LARGE_BUFF
)
shape = self.get_shape()
shape.scale_to_fit_width(self.shape_width)
shape.center()
shape.shift(SPACE_WIDTH*RIGHT/2 + self.vert_distance*UP)
big_brace = Brace(shape, self.brace_direction)
big_brace_text = big_brace.get_text("$1$")
shape_copy = shape.copy()
shape_copy.next_to(shape, DOWN, buff = self.shape_to_shape_buff)
shape_copy.scale_in_place(self.break_up_factor)
for submob in shape_copy:
submob.scale_in_place(1./self.break_up_factor)
little_brace = Brace(shape_copy[0], self.brace_direction)
little_brace_text = little_brace.get_text("$\\frac{1}{2}$")
self.add(title, scaling_factor_label, mass_scaling_label[0])
self.play(GrowFromCenter(shape))
self.play(
GrowFromCenter(big_brace),
Write(big_brace_text)
)
self.dither()
self.play(
shape.copy().replace, shape_copy[0]
)
self.remove(*self.get_mobjects_from_last_animation())
self.add(shape_copy[0])
self.play(
GrowFromCenter(little_brace),
Write(little_brace_text)
)
self.dither()
self.play(Write(mass_scaling_label[1], run_time = 1))
self.dither()
self.play(FadeIn(
VGroup(*shape_copy[1:]),
submobject_mode = "lagged_start"
))
self.dither()
self.play(Transform(
shape_copy.copy(), shape
))
self.dither()
def get_shape(self):
return VGroup(
Line(LEFT, ORIGIN),
Line(ORIGIN, RIGHT)
).highlight(BLUE)
class ScaledSquareMass(ScaledLineMass):
CONFIG = {
"title" : "Square",
"mass_scaling_factor" : "\\frac{1}{4} = \\left( \\frac{1}{2} \\right)^2",
"brace_direction" : LEFT,
}
def get_shape(self):
return VGroup(*[
Square(
stroke_width = 0,
fill_color = BLUE,
fill_opacity = 0.7
).shift(vect)
for vect in compass_directions(start_vect = DOWN+LEFT)
])
class ScaledCubeMass(ScaledLineMass):
CONFIG = {
"title" : "Cube",
"mass_scaling_factor" : "\\frac{1}{8} = \\left( \\frac{1}{2} \\right)^3",
}
def get_shape(self):
return TextMobject("TODO").set_fill(opacity = 0)
class FormCubeFromSubcubesIn3D(Scene):
def construct(self):
pass
class ScaledSierpinskiMass(ScaledLineMass):
CONFIG = {
"title" : "Sierpinski",
"mass_scaling_factor" : "\\frac{1}{3}",
"vert_distance" : 2.5,
"shape_to_shape_buff" : 1.5*LARGE_BUFF,
}
def get_shape(self):
return Sierpinski(order = 6)
class DefineTwoDimensional(PiCreatureScene):
CONFIG = {
"dimension" : "2",
"length_color" : GREEN,
"dimension_color" : YELLOW,
"shape_width" : 2,
"scale_factor" : 0.5,
"bottom_shape_buff" : MED_BUFF,
"scalar" : "s",
}
def construct(self):
self.add_title()
self.add_h_line()
self.add_shape()
self.add_width_mass_labels()
self.show_top_length()
self.change_mode("thinking")
self.perform_scaling()
self.show_dimension()
def add_title(self):
title = TextMobject(
self.dimension, "-dimensional",
arg_separator = ""
)
self.dimension_in_title = title[0]
self.dimension_in_title.highlight(self.dimension_color)
title.to_edge(UP)
self.add(title)
self.title = title
def add_h_line(self):
self.h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH)
self.add(self.h_line)
def add_shape(self):
shape = self.get_shape()
shape.scale_to_fit_width(self.shape_width)
shape.next_to(self.title, DOWN, buff = 2*MED_BUFF)
# self.shape.shift(SPACE_HEIGHT*UP/2)
self.mass_color = shape.get_color()
self.add(shape)
self.shape = shape
def add_width_mass_labels(self):
top_length = TextMobject("Length:", "$L$")
top_mass = TextMobject("Mass:", "$M$")
bottom_length = TextMobject(
"Length: ", "$%s$"%self.scalar, "$L$",
arg_separator = ""
)
bottom_mass = TextMobject(
"Mass: ",
"$%s^%s$"%(self.scalar, self.dimension),
"$M$",
arg_separator = ""
)
self.dimension_in_exp = VGroup(
*bottom_mass[1][-len(self.dimension):]
)
self.dimension_in_exp.highlight(self.dimension_color)
top_group = VGroup(top_length, top_mass)
bottom_group = VGroup(bottom_length, bottom_mass)
for group in top_group, bottom_group:
group.arrange_submobjects(
DOWN,
buff = 2*MED_BUFF,
aligned_edge = LEFT
)
group[0][-1].highlight(self.length_color)
group[1][-1].highlight(self.mass_color)
top_group.next_to(self.h_line, UP, buff = LARGE_BUFF)
bottom_group.next_to(self.h_line, DOWN, buff = LARGE_BUFF)
for group in top_group, bottom_group:
group.to_edge(LEFT)
self.add(top_group, bottom_group)
self.top_L = top_length[-1]
self.bottom_L = VGroup(*bottom_length[-2:])
self.bottom_mass = bottom_mass
def show_top_length(self):
brace = Brace(self.shape, LEFT)
top_L = self.top_L.copy()
self.play(GrowFromCenter(brace))
self.play(top_L.next_to, brace, LEFT)
self.dither()
self.brace = brace
def perform_scaling(self):
group = VGroup(self.shape, self.brace).copy()
self.play(
group.shift,
(group.get_top()[1]+self.bottom_shape_buff)*DOWN
)
shape, brace = group
bottom_L = self.bottom_L.copy()
shape.generate_target()
shape.target.scale_in_place(
self.scale_factor,
)
brace.target = Brace(shape.target, LEFT)
self.play(*map(MoveToTarget, group))
self.play(bottom_L.next_to, brace, LEFT)
self.dither()
def show_dimension(self):
top_dimension = self.dimension_in_title.copy()
self.play(self.pi_creature.look_at, top_dimension)
self.play(Transform(
top_dimension,
self.dimension_in_exp,
run_time = 2,
))
self.dither(3)
def get_shape(self):
return Square(
stroke_width = 0,
fill_color = BLUE,
fill_opacity = 0.7,
)
class DefineThreeDimensional(DefineTwoDimensional):
CONFIG = {
"dimension" : "3",
}
def get_shape(self):
return Square(
stroke_width = 0,
fill_opacity = 0
)
class DefineSierpinskiDimension(DefineTwoDimensional):
CONFIG = {
"dimension" : "D",
"scalar" : "\\left( \\frac{1}{2} \\right)",
"sierpinski_order" : 6,
"equation_scale_factor" : 1.3,
}
def construct(self):
DefineTwoDimensional.construct(self)
self.change_mode("confused")
self.dither()
self.add_one_third()
self.isolate_equation()
def add_one_third(self):
equation = TextMobject(
"$= \\left(\\frac{1}{3}\\right)$", "$M$",
arg_separator = ""
)
equation.highlight_by_tex("$M$", self.mass_color)
equation.next_to(self.bottom_mass)
self.play(Write(equation))
self.change_mode("pondering")
self.dither()
self.equation = VGroup(self.bottom_mass, equation)
self.distilled_equation = VGroup(
self.bottom_mass[1],
equation[0]
).copy()
def isolate_equation(self):
# everything = VGroup(*self.get_mobjects())
keepers = [self.pi_creature, self.equation]
for mob in keepers:
mob.save_state()
keepers_copies = [mob.copy() for mob in keepers]
self.play(
*[
ApplyMethod(mob.fade, 0.5)
for mob in self.get_mobjects()
] + [
Animation(mob)
for mob in keepers_copies
]
)
self.remove(*keepers_copies)
for mob in keepers:
ApplyMethod(mob.restore).update(1)
self.add(*keepers)
self.play(
self.pi_creature.change_mode, "confused",
self.pi_creature.look_at, self.equation
)
self.dither()
equation = self.distilled_equation
self.play(
equation.arrange_submobjects, RIGHT,
equation.scale, self.equation_scale_factor,
equation.to_corner, UP+RIGHT,
run_time = 2
)
self.dither(2)
simpler_equation = TexMobject("2^D = 3")
simpler_equation[1].highlight(self.dimension_color)
simpler_equation.scale(self.equation_scale_factor)
simpler_equation.next_to(equation, DOWN, buff = 2*MED_BUFF)
log_expression = TexMobject("\\log_2(3) \\approx", "1.585")
log_expression[-1].highlight(self.dimension_color)
log_expression.scale(self.equation_scale_factor)
log_expression.next_to(simpler_equation, DOWN, buff = 2*MED_BUFF)
log_expression.shift_onto_screen()
self.play(Write(simpler_equation))
self.change_mode("pondering")
self.dither(2)
self.play(Write(log_expression))
self.play(
self.pi_creature.change_mode, "hooray",
self.pi_creature.look_at, log_expression
)
self.dither(2)
def get_shape(self):
return Sierpinski(
order = self.sierpinski_order,
color = RED,
)
class ShowSierpinskiCurve(Scene):
CONFIG = {
"max_order" : 8,
}
def construct(self):
curve = self.get_curve(2)
self.play(ShowCreation(curve, run_time = 2))
for order in range(3, self.max_order + 1):
self.play(Transform(
curve, self.get_curve(order),
run_time = 2
))
self.dither()
def get_curve(self, order):
curve = SierpinskiCurve(order = order, monochromatic = True)
curve.highlight(RED)
return curve
class LengthAndAreaOfSierpinski(ShowSierpinskiCurve):
CONFIG = {
"curve_start_order" : 5,
"sierpinski_start_order" : 4,
"n_iterations" : 3,
}
def construct(self):
length = TextMobject("Length = $\\infty$")
length.shift(SPACE_WIDTH*LEFT/2).to_edge(UP)
area = TextMobject("Area = $0$")
area.shift(SPACE_WIDTH*RIGHT/2).to_edge(UP)
v_line = Line(UP, DOWN).scale(SPACE_HEIGHT)
self.add(length, area, v_line)
curve = self.get_curve(order = self.curve_start_order)
sierp = self.get_sierpinski(order = self.sierpinski_start_order)
self.add(curve, sierp)
self.dither()
for x in range(self.n_iterations):
new_curve = self.get_curve(order = self.curve_start_order+x+1)
alpha = (x+1.0)/self.n_iterations
stroke_width = interpolate(3, 1, alpha)
new_curve.set_stroke(width = stroke_width)
new_sierp = self.get_sierpinski(
order = self.sierpinski_start_order+x+1
)
self.play(
Transform(curve, new_curve),
Transform(sierp, new_sierp),
run_time = 2
)
self.play(sierp.set_fill, None, 0)
self.dither()
def get_curve(self, order):
# curve = ShowSierpinskiCurve.get_curve(self, order)
curve = SierpinskiCurve(order = order)
curve.scale_to_fit_height(4).center()
curve.shift(SPACE_WIDTH*LEFT/2)
return curve
def get_sierpinski(self, order):
result = Sierpinski(order = order)
result.shift(SPACE_WIDTH*RIGHT/2)
return result
class DimensionOfKoch(Scene):
CONFIG = {
"scaling_factor_color" : YELLOW,
"mass_scaling_color" : BLUE,
"dimension_color" : GREEN_A,
"curve_class" : KochCurve,
"scaling_factor" : 3,
"mass_scaling_factor" : 4,
"num_subparts" : 4,
"koch_curve_order" : 5,
"koch_curve_width" : 5,
"break_up_factor" : 1.5,
"down_shift" : 3*DOWN,
"dimension_rhs" : "\\approx 1.262",
}
def construct(self):
self.add_labels()
self.add_curve()
self.break_up_curve()
self.compare_sizes()
self.show_dimension()
def add_labels(self):
scaling_factor = TextMobject(
"Scaling factor:",
"$\\frac{1}{%d}$"%self.scaling_factor,
)
scaling_factor.next_to(ORIGIN, UP)
scaling_factor.to_edge(LEFT)
scaling_factor[1].highlight(self.scaling_factor_color)
self.add(scaling_factor[0])
mass_scaling = TextMobject(
"Mass scaling factor:",
"$\\frac{1}{%d}$"%self.mass_scaling_factor
)
mass_scaling.next_to(ORIGIN, DOWN)
mass_scaling.to_edge(LEFT)
mass_scaling[1].highlight(self.mass_scaling_color)
self.add(mass_scaling[0])
self.scaling_factor_mob = scaling_factor[1]
self.mass_scaling_factor_mob = mass_scaling[1]
def add_curve(self):
curve = self.curve_class(order = self.koch_curve_order)
curve.scale_to_fit_width(self.koch_curve_width)
curve.to_corner(UP+RIGHT, LARGE_BUFF)
self.play(ShowCreation(curve, run_time = 2))
self.dither()
self.curve = curve
def break_up_curve(self):
curve_copy = self.curve.copy()
length = len(curve_copy)
n_parts = self.num_subparts
broken_curve = VGroup(*[
VGroup(*curve_copy[i*length/n_parts:(i+1)*length/n_parts])
for i in range(n_parts)
])
self.play(broken_curve.shift, self.down_shift)
broken_curve.generate_target()
break_up(broken_curve.target, self.break_up_factor)
broken_curve.target.shift_onto_screen
self.play(MoveToTarget(broken_curve))
self.dither()
self.add(broken_curve)
self.broken_curve = broken_curve
def compare_sizes(self):
big_brace = Brace(self.curve, DOWN)
one = big_brace.get_text("$1$")
little_brace = Brace(self.broken_curve[0], DOWN)
one_third = little_brace.get_text("1/%d"%self.scaling_factor)
one_third.highlight(self.scaling_factor_color)
self.play(
GrowFromCenter(big_brace),
GrowFromCenter(little_brace),
Write(one),
Write(one_third),
)
self.dither()
self.play(Write(self.scaling_factor_mob))
self.dither()
self.play(Write(self.mass_scaling_factor_mob))
self.dither()
def show_dimension(self):
raw_formula = TexMobject("""
\\left( \\frac{1}{%s} \\right)^D
=
\\left( \\frac{1}{%s} \\right)
"""%(self.scaling_factor, self.mass_scaling_factor))
formula = VGroup(
VGroup(*raw_formula[:5]),
VGroup(raw_formula[5]),
VGroup(raw_formula[6]),
VGroup(*raw_formula[7:]),
)
formula.to_corner(UP+LEFT)
simpler_formula = TexMobject(
str(self.scaling_factor),
"^D", "=",
str(self.mass_scaling_factor)
)
simpler_formula.move_to(formula, UP)
for mob in formula, simpler_formula:
mob[0].highlight(self.scaling_factor_color)
mob[1].highlight(self.dimension_color)
mob[3].highlight(self.mass_scaling_color)
log_expression = TexMobject(
"D = \\log_%d(%d) %s"%(
self.scaling_factor,
self.mass_scaling_factor,
self.dimension_rhs
)
)
log_expression[0].highlight(self.dimension_color)
log_expression[5].highlight(self.scaling_factor_color)
log_expression[7].highlight(self.mass_scaling_color)
log_expression.next_to(
simpler_formula, DOWN,
aligned_edge = LEFT,
buff = 2*MED_BUFF
)
third = self.scaling_factor_mob.copy()
fourth = self.mass_scaling_factor_mob.copy()
for mob in third, fourth:
mob.add(VectorizedPoint(mob.get_right()))
mob.add_to_back(VectorizedPoint(mob.get_left()))
self.play(
Transform(third, formula[0]),
Transform(fourth, formula[-1]),
)
self.play(*map(FadeIn, formula[1:-1]))
self.remove(third, fourth)
self.add(formula)
self.dither(2)
self.play(Transform(formula, simpler_formula))
self.dither(2)
self.play(Write(log_expression))
self.dither(2)
class DimensionOfQuadraticKoch(DimensionOfKoch):
CONFIG = {
"curve_class" : QuadraticKoch,
"scaling_factor" : 4,
"mass_scaling_factor" : 8,
"num_subparts" : 8,
"koch_curve_order" : 4,
"koch_curve_width" : 4,
"break_up_factor" : 1.7,
"down_shift" : 4*DOWN,
"dimension_rhs" : "= \\frac{3}{2} = 1.5",
}
def construct(self):
self.add_labels()
self.add_curve()
self.highlight_curve_subparts()
self.show_dimension()
def get_curve(self, order):
curve = self.curve_class(
order = order,
monochromatic = True
)
curve.scale_to_fit_width(self.koch_curve_width)
alpha = float(order) / self.koch_curve_order
stroke_width = interpolate(3, 1, alpha)
curve.set_stroke(width = stroke_width)
return curve
def add_curve(self):
seed_label = TextMobject("Seed")
seed_label.shift(SPACE_WIDTH*RIGHT/2).to_edge(UP)
seed = self.get_curve(order = 1)
seed.next_to(seed_label, DOWN)
curve = seed.copy()
resulting_fractal = TextMobject("Resulting fractal")
resulting_fractal.shift(SPACE_WIDTH*RIGHT/2)
self.add(seed_label, seed)
self.dither()
self.play(
curve.next_to, resulting_fractal, DOWN, 2*MED_BUFF,
Write(resulting_fractal, run_time = 1)
)
for order in range(2, self.koch_curve_order+1):
new_curve = self.get_curve(order)
new_curve.move_to(curve)
n_anchors = len(curve.get_anchors())
curve.insert_n_anchor_points(6*n_anchors)
curve.make_jagged()
self.play(Transform(curve, new_curve, run_time = 2))
self.dither()
self.curve = curve
def highlight_curve_subparts(self):
n_parts = self.num_subparts
colored_curve = self.curve_class(
order = self.koch_curve_order,
stroke_width = 1
)
colored_curve.replace(self.curve)
length = len(colored_curve)
broken_curve = VGroup(*[
VGroup(*colored_curve[i*length/n_parts:(i+1)*length/n_parts])
for i in range(n_parts)
])
colors = it.cycle([WHITE, RED])
for subpart, color in zip(broken_curve, colors):
subpart.highlight(color)
self.play(
FadeOut(self.curve),
FadeIn(colored_curve)
)
self.play(
ApplyFunction(
lambda m : break_up(m, self.break_up_factor),
broken_curve,
rate_func = there_and_back,
run_time = 2
)
)
self.dither()
self.play(Write(self.scaling_factor_mob))
self.play(Write(self.mass_scaling_factor_mob))
self.dither(2)
class ThisIsSelfSimilarityDimension(TeacherStudentsScene):
def construct(self):
self.teacher_says("""
This is called
``self-similarity dimension''
""")
self.change_student_modes(*["pondering"]*3)
self.dither(2)
class ShowDiskScaling(Scene):
def construct(self):
self.show_non_self_similar_shapes()
self.isolate_disk()
self.scale_disk()
self.write_mass_scaling_factor()
self.try_fitting_small_disks()
def show_non_self_similar_shapes(self):
title = TextMobject(
"Most shapes are not self-similar"
)
title.to_edge(UP)
self.add(title)
hexagon = RegularPolygon(n = 6)
disk = Circle()
blob = VMobject().set_points_smoothly([
RIGHT, RIGHT+UP, ORIGIN, RIGHT+DOWN, LEFT, UP, RIGHT
])
britain = Britain()
shapes = VGroup(hexagon, blob, disk, britain)
for shape in shapes:
shape.scale_to_fit_width(1.5)
shape.set_stroke(width = 0)
shape.set_fill(opacity = 1)
shapes.gradient_highlight(BLUE_B, BLUE_E)
shapes.arrange_submobjects(RIGHT, buff = LARGE_BUFF)
shapes.next_to(title, DOWN)
for shape in shapes:
self.play(FadeIn(shape))
self.dither(2)
self.disk = disk
self.to_fade = VGroup(
title, hexagon, blob, britain
)
def isolate_disk(self):
disk = self.disk
self.play(
FadeOut(self.to_fade),
disk.scale_to_fit_width, 2,
disk.next_to, ORIGIN, LEFT, 2,
disk.set_fill, BLUE_D, 0.7
)
radius = Line(
disk.get_center(), disk.get_right(),
color = YELLOW
)
one = TexMobject("1").next_to(radius, DOWN, SMALL_BUFF)
self.play(ShowCreation(radius))
self.play(Write(one))
self.dither()
self.disk.add(radius, one)
def scale_disk(self):
scaled_disk = self.disk.copy()
scaled_disk.generate_target()
scaled_disk.target.scale(2)
scaled_disk.target.next_to(ORIGIN, RIGHT)
one = scaled_disk.target[-1]
two = TexMobject("2")
two.move_to(one, UP)
scaled_disk.target.submobjects[-1] = two
self.play(MoveToTarget(scaled_disk))
self.dither()
self.scaled_disk = scaled_disk
def write_mass_scaling_factor(self):
mass_scaling = TextMobject(
"Mass scaling factor: $2^2 = 4$"
)
mass_scaling.next_to(self.scaled_disk, UP)
mass_scaling.to_edge(UP)
self.play(Write(mass_scaling))
self.dither()
def try_fitting_small_disks(self):
disk = self.disk.copy()
disk.submobjects = []
disk.set_fill(opacity = 0.5)
foursome = VGroup(*[
disk.copy().next_to(ORIGIN, vect, buff = 0)
for vect in compass_directions(start_vect = UP+RIGHT)
])
foursome.move_to(self.scaled_disk)
self.play(Transform(disk, foursome))
self.remove(*self.get_mobjects_from_last_animation())
self.add(foursome)
self.dither()
self.play(ApplyFunction(
lambda m : break_up(m, 0.2),
foursome,
rate_func = there_and_back,
run_time = 4,
))
self.dither()
self.play(FadeOut(foursome))
self.dither()
class WhatDoYouMeanByMass(TeacherStudentsScene):
def construct(self):
self.student_says(
"What do you mean \\\\ by mass?",
target_mode = "sassy"
)
self.change_student_modes("pondering", "sassy", "confused")
self.dither()
self.play(self.get_teacher().change_mode, "thinking")
self.dither(2)
self.teacher_thinks("")
self.zoom_in_on_thought_bubble()
class BoxCountingScene(Scene):
CONFIG = {
"box_width" : 0.25,
"box_color" : YELLOW,
"box_opacity" : 0.5,
"num_boundary_check_points" : 200,
"corner_rect_left_extension" : 0,
}
def setup(self):
self.num_rows = 2*int(SPACE_HEIGHT/self.box_width)+1
self.num_cols = 2*int(SPACE_WIDTH/self.box_width)+1
def get_grid(self):
v_line = Line(UP, DOWN).scale(SPACE_HEIGHT)
v_lines = VGroup(*[
v_line.copy().shift(u*x*self.box_width*RIGHT)
for x in range(self.num_cols/2+1)
for u in [-1, 1]
])
h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH)
h_lines = VGroup(*[
h_line.copy().shift(u*y*self.box_width*UP)
for y in range(self.num_rows/2+1)
for u in [-1, 1]
])
grid = VGroup(v_lines, h_lines)
if self.box_width > 0.2:
grid.set_stroke(width = 1)
else:
grid.set_stroke(width = 0.5)
return grid
def get_highlighted_boxes(self, vmobject):
points = []
if vmobject.stroke_width > 0:
for submob in vmobject.family_members_with_points():
alphas = np.linspace(0, 1, self.num_boundary_check_points)
points += [
submob.point_from_proportion(alpha)
for alpha in alphas
]
if vmobject.fill_opacity > 0:
camera = Camera(**LOW_QUALITY_CAMERA_CONFIG)
camera.capture_mobject(vmobject)
box_centers = self.get_box_centers()
pixel_coords = camera.points_to_pixel_coords(box_centers)
for index, (x, y) in enumerate(pixel_coords):
try:
rgb = camera.pixel_array[y, x]
if not np.all(rgb == np.zeros(3)):
points.append(box_centers[index])
except:
pass
return self.get_boxes(points)
def get_box_centers(self):
bottom_left = reduce(op.add, [
self.box_width*(self.num_cols/2)*LEFT,
self.box_width*(self.num_rows/2)*DOWN,
self.box_width*RIGHT/2,
self.box_width*UP/2,
])
return np.array([
bottom_left + (x*RIGHT+y*UP)*self.box_width
for x in range(self.num_cols)
for y in range(self.num_rows)
])
def get_boxes(self, points):
points = np.array(points)
rounded_points = np.floor(points/self.box_width)*self.box_width
unique_rounded_points = np.vstack({
tuple(row) for
row in rounded_points
})
return VGroup(*[
Square(
side_length = self.box_width,
stroke_width = 0,
fill_color = self.box_color,
fill_opacity = self.box_opacity,
).move_to(point, DOWN+LEFT)
for point in unique_rounded_points
])
def get_corner_rect(self):
rect = Rectangle(
height = SPACE_HEIGHT/2,
width = SPACE_WIDTH+self.corner_rect_left_extension,
stroke_width = 0,
fill_color = BLACK,
fill_opacity = 0.8
)
rect.to_corner(UP+RIGHT, buff = 0)
return rect
def get_counting_label(self):
label = TextMobject("Boxes touched:")
label.next_to(ORIGIN, RIGHT)
label.to_edge(UP)
label.shift(self.corner_rect_left_extension*LEFT)
self.counting_num_reference = label[-1]
rect = BackgroundRectangle(label)
rect.stretch(1.3, 0)
rect.move_to(label, LEFT)
label.add_to_back(rect)
return label
def count_boxes(self, boxes):
num = DecimalNumber(len(boxes), num_decimal_points = 0)
num.next_to(boxes, RIGHT)
num.add_to_back(BackgroundRectangle(num))
self.play(ShowCreation(boxes, run_time = 3))
self.play(Write(num))
self.play(
num.next_to, self.counting_num_reference, RIGHT, MED_BUFF, DOWN,
num.highlight, YELLOW
)
return num
class BoxCountingWithDisk(BoxCountingScene):
CONFIG = {
"box_width" : 0.25,
"num_boundary_check_points" : 200,
}
def construct(self):
disk = Circle(radius = 1)
disk.set_fill(BLUE, opacity = 0.5)
disk.set_stroke(BLUE, width = 1)
radius = Line(disk.get_center(), disk.get_right())
disk.add(radius)
one = TexMobject("1").next_to(radius, DOWN, SMALL_BUFF)
boxes = self.get_highlighted_boxes(disk)
grid = self.get_grid()
corner_rect = self.get_corner_rect()
counting_label = self.get_counting_label()
self.add(disk, one)
self.play(
ShowCreation(grid),
Animation(disk),
)
self.dither()
self.play(
FadeIn(corner_rect),
FadeIn(counting_label)
)
counting_mob = self.count_boxes(boxes)
self.dither(2)
disk.generate_target()
disk.target.scale(2, about_point = disk.get_top())
two = TexMobject("2").next_to(disk.target[1], DOWN, SMALL_BUFF)
self.play(
MoveToTarget(disk),
Transform(one, two),
FadeOut(boxes),
)
self.play(counting_mob.next_to, counting_mob, DOWN)
boxes = self.get_highlighted_boxes(disk)
new_counting_mob = self.count_boxes(boxes)
self.dither()
frac_line = TexMobject("-")
frac_line.highlight(YELLOW)
frac_line.stretch_to_fit_width(new_counting_mob.get_width())
frac_line.next_to(new_counting_mob, DOWN, buff = SMALL_BUFF)
approx = TexMobject("\\approx 2^2")
approx.next_to(frac_line, RIGHT)
approx.shift_onto_screen()
self.play(*map(Write, [frac_line, approx]))
self.dither(2)
class FinerBoxCountingWithDisk(BoxCountingWithDisk):
CONFIG = {
"box_width" : 0.1
}
class PlotDiskBoxCounting(GraphScene):
CONFIG = {
"x_axis_label" : "Scaling factor",
"y_axis_label" : "Number of boxes \\\\ touched",
"x_labeled_nums" : [],
"y_labeled_nums" : [],
"x_min" : 0,
"y_min" : 0,
"y_max" : 30,
"func" : lambda x : 0.5*x**2,
"func_label" : "f(x) = cx^2",
}
def construct(self):
self.plot_points()
self.describe_better_fit()
def plot_points(self):
self.setup_axes()
self.graph_function(self.func)
self.remove(self.graph)
data_points = [
self.input_to_graph_point(x) + ((random.random()-0.5)/x)*UP
for x in np.arange(2, 10, 0.5)
]
data_dots = VGroup(*[
Dot(point, radius = 0.05, color = YELLOW)
for point in data_points
])
self.play(ShowCreation(data_dots))
self.dither()
self.play(ShowCreation(self.graph))
self.label_graph(
self.graph,
self.func_label,
direction = RIGHT+DOWN,
buff = SMALL_BUFF,
color = WHITE,
)
self.dither()
def describe_better_fit(self):
words = TextMobject("Better fit at \\\\ higher inputs")
arrow = Arrow(2*LEFT, 2*RIGHT)
arrow.next_to(self.x_axis_label_mob, UP)
arrow.shift(2*LEFT)
words.next_to(arrow, UP)
self.play(ShowCreation(arrow))
self.play(Write(words))
self.dither(2)
class FineGridSameAsLargeScaling(BoxCountingScene):
CONFIG = {
"box_width" : 0.25/6,
"scale_factor" : 6
}
def construct(self):
disk = Circle(radius = 1)
disk.set_fill(BLUE, opacity = 0.5)
disk.set_stroke(BLUE, width = 1)
grid = self.get_grid()
grid.scale(self.scale_factor)
self.add(grid, disk)
self.dither()
self.play(disk.scale, self.scale_factor)
self.dither()
self.play(
grid.scale, 1./self.scale_factor,
disk.scale, 1./self.scale_factor,
disk.set_stroke, None, 0.5,
)
self.dither()
boxes = self.get_highlighted_boxes(disk)
self.play(ShowCreation(boxes, run_time = 3))
self.dither(2)
class BoxCountingSierpinski(BoxCountingScene):
CONFIG = {
"box_width" : 0.1,
"sierpinski_order" : 7,
"sierpinski_width" : 3,
"num_boundary_check_points" : 6,
"corner_rect_left_extension" : 2,
}
def construct(self):
self.add(self.get_grid())
sierp = Sierpinski(order = self.sierpinski_order)
sierp.set_fill(opacity = 0)
sierp.move_to(3*DOWN, DOWN+RIGHT)
sierp.scale_to_fit_width(self.sierpinski_width)
boxes = self.get_highlighted_boxes(sierp)
corner_rect = self.get_corner_rect()
counting_label = self.get_counting_label()
self.play(ShowCreation(sierp))
self.play(*map(FadeIn, [corner_rect, counting_label]))
self.dither()
counting_mob = self.count_boxes(boxes)
self.dither()
self.play(
FadeOut(boxes),
sierp.scale, 2, sierp.get_corner(DOWN+RIGHT),
)
self.play(counting_mob.next_to, counting_mob, DOWN)
boxes = self.get_highlighted_boxes(sierp)
new_counting_mob = self.count_boxes(boxes)
self.dither()
frac_line = TexMobject("-")
frac_line.highlight(YELLOW)
frac_line.stretch_to_fit_width(new_counting_mob.get_width())
frac_line.next_to(new_counting_mob, DOWN, buff = SMALL_BUFF)
approx_three = TexMobject("\\approx 3")
approx_three.next_to(frac_line, RIGHT)
equals_exp = TexMobject("= 2^{1.585...}")
equals_exp.next_to(approx_three, RIGHT, aligned_edge = DOWN)
equals_exp.shift_onto_screen()
self.play(*map(Write, [frac_line, approx_three]))
self.dither()
self.play(Write(equals_exp))
self.dither()
class PlotSierpinskiBoxCounting(PlotDiskBoxCounting):
CONFIG = {
"func" : lambda x : 0.5*x**1.585,
"func_label" : "f(x) = cx^{1.585}",
}
def construct(self):
self.plot_points()
class BoxCountingWithBritain(BoxCountingScene):
CONFIG = {
"box_width" : 0.1,
"num_boundary_check_points" : 5000,
"corner_rect_left_extension" : 1,
}
def construct(self):
self.show_box_counting()
self.show_formula()
def show_box_counting(self):
self.add(self.get_grid())
britain = Britain(
stroke_width = 2,
fill_opacity = 0
)
britain = fractalify(britain, order = 1, dimension = 1.21)
britain.shift(DOWN+LEFT)
boxes = self.get_highlighted_boxes(britain)
self.play(ShowCreation(britain, run_time = 3))
self.dither()
self.play(ShowCreation(boxes, run_time = 3))
self.dither()
self.play(FadeOut(boxes))
self.play(britain.scale, 2.5, britain.get_corner(DOWN+RIGHT))
boxes = self.get_highlighted_boxes(britain)
self.play(ShowCreation(boxes, run_time = 2))
self.dither()
def show_formula(self):
corner_rect = self.get_corner_rect()
equation = TextMobject("""
Number of boxes $\\approx$
\\quad $c(\\text{scaling factor})^{1.21}$
""")
equation.next_to(
corner_rect.get_corner(UP+LEFT), DOWN+RIGHT
)
N = equation[0].copy()
word_len = len("Numberofboxes")
approx = equation[word_len].copy()
c = equation[word_len+1].copy()
s = equation[word_len+3].copy()
dim = VGroup(*equation[-len("1.21"):]).copy()
N.highlight(YELLOW)
s.highlight(BLUE)
dim.highlight(GREEN)
simpler_eq = VGroup(N, approx, c, s, dim)
simpler_eq.generate_target()
simpler_eq.target.arrange_submobjects(buff = SMALL_BUFF)
simpler_eq.target.move_to(N, LEFT)
simpler_eq.target[-1].next_to(
simpler_eq.target[-2].get_corner(UP+RIGHT),
RIGHT,
buff = SMALL_BUFF
)
self.play(
FadeIn(corner_rect),
Write(equation)
)
self.dither(2)
self.play(FadeIn(simpler_eq))
self.dither()
self.play(
FadeOut(equation),
Animation(simpler_eq)
)
self.play(MoveToTarget(simpler_eq))
self.dither(2)
log_expression1 = TexMobject(
"\\log(", "N", ")", "=",
"\\log(", "c", "s", "^{1.21}", ")"
)
log_expression2 = TexMobject(
"\\log(", "N", ")", "=",
"\\log(", "c", ")", "+",
"1.21", "\\log(", "s", ")"
)
for log_expression in log_expression1, log_expression2:
log_expression.next_to(simpler_eq, DOWN, aligned_edge = LEFT)
log_expression.highlight_by_tex("N", N.get_color())
log_expression.highlight_by_tex("s", s.get_color())
log_expression.highlight_by_tex("^{1.21}", dim.get_color())
log_expression.highlight_by_tex("1.21", dim.get_color())
rewired_log_expression1 = VGroup(*[
log_expression1[index].copy()
for index in [
0, 1, 2, 3, #match with log_expression2
4, 5, 8, 8,
7, 4, 6, 8
]
])
self.play(Write(log_expression1))
self.remove(log_expression1)
self.add(rewired_log_expression1)
self.dither()
self.play(Transform(
rewired_log_expression1,
log_expression2,
run_time = 2
))
self.dither(2)
self.final_expression = VGroup(
simpler_eq, rewired_log_expression1
)
class CheapBoxCountingWithBritain(BoxCountingWithBritain):
CONFIG = {
"skip_animations" : True,
}
def construct(self):
self.show_formula()
class ConfusedAtParabolicData(PlotDiskBoxCounting):
CONFIG = {
"func" : lambda x : 0.5*x**1.6,
"func_label" : "f(x) = cx^{1.21}",
}
def construct(self):
self.plot_points()
randy = Randolph()
randy.to_corner(DOWN+LEFT)
randy.shift(RIGHT)
self.play(FadeIn(randy))
self.play(randy.change_mode, "confused")
self.play(randy.look_at, self.x_axis_label_mob)
self.play(Blink(randy))
self.dither(2)
class IntroduceLogLogPlot(GraphScene):
CONFIG = {
"x_axis_label" : "\\log(s)",
"y_axis_label" : "\\log(N)",
"x_labeled_nums" : [],
"y_labeled_nums" : [],
"graph_origin" : 2.5*DOWN+6*LEFT,
"dimension" : 1.21,
"y_intercept" : 2,
"x_max" : 16,
}
def construct(self):
last_scene = CheapBoxCountingWithBritain()
expression = last_scene.final_expression
box = Rectangle(
stroke_color = WHITE,
fill_color = BLACK,
fill_opacity = 0.7,
)
box.replace(expression, stretch = True)
box.scale_in_place(1.2)
expression.add_to_back(box)
self.add(expression)
self.setup_axes(animate = False)
self.x_axis_label_mob[-2].highlight(BLUE)
self.y_axis_label_mob[-2].highlight(YELLOW)
graph = self.graph_function(
lambda x : self.y_intercept+self.dimension*x
)
self.remove(graph)
p1 = self.input_to_graph_point(2)
p2 = self.input_to_graph_point(3)
interim_point = p2[0]*RIGHT + p1[1]*UP
h_line = Line(p1, interim_point)
v_line = Line(interim_point, p2)
slope_lines = VGroup(h_line, v_line)
slope_lines.highlight(GREEN)
slope = TextMobject("Slope = ", "$%.2f$"%self.dimension)
slope[-1].highlight(GREEN)
slope.next_to(slope_lines, RIGHT)
self.dither()
self.play(
ShowCreation(graph),
Animation(expression)
)
self.dither()
self.play(ShowCreation(slope_lines))
self.play(Write(slope))
self.dither()
data_points = [
self.input_to_graph_point(x) + ((random.random()-0.5)/x)*UP
for x in np.arange(1, 8, 0.7)
]
data_dots = VGroup(*[
Dot(point, radius = 0.05, color = YELLOW)
for point in data_points
])
self.play(ShowCreation(data_dots, run_time = 3))
self.dither()
class ZoomInOnBritain(Scene):
def construct(self):
britain = Britain()
fractalify(britain, order = 3, dimension = 1.21)
point = britain.point_from_proportion(0.3)
self.add(britain)
self.dither()
self.play(
britain.scale, 100, point,
run_time = 10
)
self.dither()
class FromHandwavyToQuantitative(Scene):
def construct(self):
randy = Randolph()
morty = Mortimer()
for pi in randy, morty:
pi.next_to(ORIGIN, DOWN)
randy.shift(2*LEFT)
morty.shift(2*RIGHT)
randy.make_eye_contact(morty)
self.add(randy, morty)
self.play(PiCreatureSays(
randy, "Fractals are rough",
target_mode = "shruggie"
))
self.play(morty.change_mode, "sassy")
self.play(Blink(morty))
self.play(
PiCreatureSays(
morty, "We can make \\\\ that quantitative!",
target_mode = "hooray"
),
FadeOut(randy.bubble),
FadeOut(randy.bubble.content),
randy.change_mode, "happy"
)
self.play(Blink(randy))
self.dither()
class WhatSlopeDoesLogLogPlotApproach(IntroduceLogLogPlot):
def construct(self):
self.setup_axes(animate = False)
self.x_axis_label_mob[-2].highlight(BLUE)
self.y_axis_label_mob[-2].highlight(YELLOW)
graph = self.graph_function(
lambda x : self.y_intercept+self.dimension*x
)
self.remove(graph)
class IfBritainWasEventuallySmooth(Scene):
def construct(self):
britain = Britain()
britain.make_smooth()
point = britain.point_from_proportion(0.3)
self.add(britain)
self.dither()
self.play(
britain.scale, 200, point,
run_time = 10
)
self.dither()
class SmoothBritainLogLogPlot(IntroduceLogLogPlot):
CONFIG = {
}
def construct(self):
self.setup_axes()
self.graph_function(
lambda x : (1 + np.exp(-x/5.0))*x
)
self.remove(self.graph)
p1, p2, p3, p4 = [
self.input_to_graph_point(x)
for x in 1, 2, 7, 8
]
interim_point1 = p2[0]*RIGHT + p1[1]*UP
interim_point2 = p4[0]*RIGHT + p3[1]*UP
print self.func(2)
slope_lines1, slope_lines2 = VMobject(), VMobject()
slope_lines1.set_points_as_corners(
[p1, interim_point1, p2]
)
slope_lines2.set_points_as_corners(
[p3, interim_point2, p4]
)
slope_lines_group = VGroup(slope_lines1, slope_lines2)
slope_lines_group.highlight(GREEN)
slope_label1 = TextMobject("Slope $> 1$")
slope_label2 = TextMobject("Slope $= 1$")
slope_label1.next_to(slope_lines1)
slope_label2.next_to(slope_lines2)
data_points = [
self.input_to_graph_point(x) + ((random.random()-0.5)/x)*UP
for x in np.arange(1, 12, 0.7)
]
data_dots = VGroup(*[
Dot(point, radius = 0.05, color = YELLOW)
for point in data_points
])
self.play(ShowCreation(data_dots, run_time = 3))
self.play(ShowCreation(self.graph))
self.dither()
self.play(
ShowCreation(slope_lines1),
Write(slope_label1)
)
self.dither()
self.play(
ShowCreation(slope_lines2),
Write(slope_label2)
)
self.dither()
class ChangeWorldview(TeacherStudentsScene):
def construct(self):
self.teacher_says("""
This changes how
you see the world.
""")
self.change_student_modes(*["thinking"]*3)
self.dither(3)
class CompareBritainAndNorway(Scene):
def construct(self):
norway = Norway(
fill_opacity = 0,
stroke_width = 2,
)
norway.to_corner(UP+RIGHT, buff = 0)
fractalify(norway, order = 1, dimension = 1.5)
anchors = list(norway.get_anchors())
anchors.append(SPACE_WIDTH*RIGHT+SPACE_HEIGHT*UP)
norway.set_points_as_corners(anchors)
britain = Britain(
fill_opacity = 0,
stroke_width = 2
)
britain.shift(SPACE_WIDTH*LEFT/2)
britain.to_edge(UP)
fractalify(britain, order = 1, dimension = 1.21)
britain_label = TextMobject("""
Britain coast:
1.21-dimensional
""")
norway_label = TextMobject("""
Norway coast:
1.52-dimensional
""")
britain_label.next_to(britain, DOWN)
norway_label.next_to(britain_label, RIGHT, aligned_edge = DOWN)
norway_label.to_edge(RIGHT)
self.add(britain_label, norway_label)
self.play(
*map(ShowCreation, [norway, britain]),
run_time = 3
)
self.dither()
self.play(*it.chain(*[
[
mob.set_stroke, None, 0,
mob.set_fill, BLUE, 1
]
for mob in britain, norway
]))
self.dither(2)
class CompareOceansLabels(Scene):
def construct(self):
label1 = TextMobject("Dimension $\\approx 2.05$")
label2 = TextMobject("Dimension $\\approx 2.3$")
label1.shift(SPACE_WIDTH*LEFT/2).to_edge(UP)
label2.shift(SPACE_WIDTH*RIGHT/2).to_edge(UP)
self.play(Write(label1))
self.dither()
self.play(Write(label2))
self.dither()
class CompareOceans(Scene):
def construct(self):
pass
class DefineFractal(TeacherStudentsScene):
def construct(self):
fractal = TextMobject("Fractal:")
fractal.scale(2)
fractal.to_edge(UP)
definition = TextMobject(
"A shapes with a", "non-integer dimension\\\\",
"that stays", "constant at all scales.",
alignment = ""
)
definition[1].highlight(GREEN)
definition[3].highlight(BLUE)
definition.shift(UP)
self.add(fractal)
for pi in self.get_everyone():
pi.look_at(fractal)
self.dither()
self.play(
Write(VGroup(*definition[:2])),
self.get_teacher().change_mode, "raise_right_hand"
)
self.change_student_modes("hesitant", "pondering", "happy")
self.play(
Write(VGroup(*definition[2:])),
*[
ApplyMethod(pi.look_at, definition)
for pi in self.get_everyone()
]
)
self.dither(3)
class MortyLookingAtRectangle(Scene):
def construct(self):
morty = Mortimer()
morty.to_corner(DOWN+RIGHT)
url = TextMobject("affirmjobs.3b1b.co")
url.to_corner(UP+LEFT)
rect = Rectangle(height = 9, width = 16)
rect.scale_to_fit_height(5)
rect.next_to(url, DOWN)
rect.shift_onto_screen()
url.save_state()
url.next_to(morty.get_corner(UP+LEFT), UP)
self.play(morty.change_mode, "raise_right_hand")
self.play(Write(url))
self.play(Blink(morty))
self.dither()
self.play(
url.restore,
morty.change_mode, "happy"
)
self.play(ShowCreation(rect))
self.dither()
self.play(Blink(morty))
for mode in ["wave_2", "hooray", "happy", "pondering", "happy"]:
self.play(morty.change_mode, mode)
self.dither(2)
self.play(Blink(morty))
self.dither(2)