Merge branch 'master' of github.com:3b1b/manim into wallis-g

This commit is contained in:
Grant Sanderson
2018-04-12 15:12:07 -07:00
6 changed files with 670 additions and 225 deletions

View File

@ -1,5 +1,9 @@
from big_ol_pile_of_manim_imports import *
from old_projects.eoc.chapter8 import *
from active_projects.eop.histograms import *
from svgpathtools import *
import scipy.special
COIN_RADIUS = 0.3
COIN_THICKNESS = 0.4 * COIN_RADIUS
@ -665,19 +669,35 @@ class IllustrateAreaModel1(Scene):
label_B_knowing_A = label_B
self.play(FadeOut(label_B_copy))
self.remove(indep_formula.get_part_by_tex("P(B)"))
label_B_knowing_A_copy = label_B_knowing_A.copy()
self.add(label_B_knowing_A_copy)
self.play(
label_B_knowing_A_copy.next_to, indep_formula[-2], RIGHT
label_B_knowing_A_copy.next_to, indep_formula.get_part_by_tex("\cdot"), RIGHT,
)
# solve formula for P(B|A)
rearranged_formula = TexMobject(["P(B\mid A)", "=", "{P(A\\text{ and }B) \over P(A)}"])
rearranged_formula.move_to(indep_formula)
self.wait()
self.play(
# in some places get_part_by_tex does not find the correct part
# so I picked out fitting indices
label_B_knowing_A_copy.move_to, rearranged_formula.get_part_by_tex("P(B\mid A)"),
label_A_copy.move_to, rearranged_formula[-1][10],
label_A_and_B_copy.move_to, rearranged_formula[-1][3],
indep_formula.get_part_by_tex("=").move_to, rearranged_formula.get_part_by_tex("="),
Transform(indep_formula.get_part_by_tex("\cdot"), rearranged_formula[-1][8]),
)
# # # # # # # # # # # # # # # # #
# Old version with SampleSpace #
# # # # # # # # # # # # # # # # #
# def show_independent_events(self):
# sample_space = SampleSpace(
@ -747,62 +767,200 @@ class IllustrateAreaModel1(Scene):
def color_label(self, label):
label.set_color_by_tex("B", RED)
label.set_color_by_tex("I", GREEN)
# def color_label(self, label):
# label.set_color_by_tex("B", RED)
# label.set_color_by_tex("I", GREEN)
class IllustrateAreaModel2(AreaIsDerivative):
class IllustrateAreaModel2(GraphScene):
CONFIG = {
"y_max" : 4,
"y_min" : -4,
"num_iterations" : 7,
"x_min" : -5,
"x_max" : 5,
"y_min" : -0,
"y_max" : 0.6,
"graph_origin": 3*DOWN,
"num_rects": 20,
"y_axis_label" : "",
"num_rects" : 400,
"dT" : 0.25,
"variable_point_label" : "T",
"area_opacity" : 0.8,
"x_axis_label" : "",
"variable_point_label" : "x",
"y_axis_height" : 4
}
def construct(self):
x_max_1 = 0
x_min_1 = -x_max_1
x_max_2 = 5
x_min_2 = -x_max_2
self.setup_axes()
self.introduce_variable_area()
graph = self.get_graph(lambda x: np.exp(-x**2) / ((0.5 * TAU) ** 0.5))
graph, label = self.get_v_graph_and_label()
self.add(graph)
rect_list = self.get_riemann_rectangles_list(
graph, self.num_iterations
cdf_formula = TexMobject("P(|X-\mu| < x) = \int_{-x}^x {\exp(-{1\over 2}({t\over \sigma})^2) \over \sigma\sqrt{2\pi}} dt")
cdf_formula.set_color_by_tex("x", YELLOW)
cdf_formula.next_to(graph, LEFT, buff = 1)
self.add(cdf_formula)
self.v_graph = graph
self.add_T_label(x_min_1, color = YELLOW, animated = False)
self.remove(self.T_label_group, self.right_v_line)
#self.T_label_group[0].set_fill(opacity = 0).set_stroke(width = 0)
#self.T_label_group[1].set_fill(opacity = 0).set_stroke(width = 0)
#self.right_v_line.set_fill(opacity = 0).set_stroke(width = 0)
#self.add(self.T_label_group)
area = self.area = self.get_area(graph, x_min_1, x_max_1)
right_bound_label = TexMobject("x", color = YELLOW)
right_bound_label.next_to(self.coords_to_point(0,0), DOWN)
right_bound_label.target = right_bound_label.copy().next_to(self.coords_to_point(self.x_max,0), DOWN)
right_bound_label.set_fill(opacity = 0).set_stroke(width = 0)
left_bound_label = TexMobject("-x", color = YELLOW)
left_bound_label.next_to(self.coords_to_point(0,0), DOWN)
left_bound_label.target = right_bound_label.copy().next_to(self.coords_to_point(self.x_min,0), DOWN)
left_bound_label.set_fill(opacity = 0).set_stroke(width = 0)
#integral = self.get_riemann_rectangles(
#graph,x_min = self.x_min, x_max = x_max_1)
self.add(area)
def integral_update_func(t):
return 100 * scipy.special.erf(
self.point_to_coords(self.right_v_line.get_center())[0]
)
VGroup(*rect_list).set_fill(opacity = 0.8)
rects = rect_list[0]
self.play(ShowCreation(graph))
self.play(Write(rects))
for new_rects in rect_list[1:]:
rects.align_submobjects(new_rects)
for every_other_rect in rects[::2]:
every_other_rect.set_fill(opacity = 0)
self.play(Transform(
rects, new_rects,
run_time = 2,
submobject_mode = "lagged_start"
cdf_value = DecimalNumber(0, unit = "\%")
cdf_value.move_to(self.coords_to_point(0,0.2))
self.add_foreground_mobject(cdf_value)
self.add(ContinualChangingDecimal(
decimal_number_mobject = cdf_value,
number_update_func = integral_update_func,
num_decimal_points = 1
))
self.wait()
# self.play(FadeOut(self.x_axis.numbers))
self.add_T_label(6)
self.change_area_bounds(
new_t_max = 4,
rate_func = there_and_back,
run_time = 2
anim = self.get_animation_integral_bounds_change(
graph, x_min_2, x_max_2, run_time = 3)
# changing_cdf_value = ChangingDecimal(
# decimal_number_mobject = cdf_value,
# number_update_func = integral_update_func,
# num_decimal_points = 1
# )
self.play(
anim
)
def func(self, x):
return np.exp(-x**2/2)
class IllustrateAreaModel3(Scene):
def construct(self):
formula = TexMobject("E[X] = \sum_{i=1}^N p_i x_i").move_to(3 * LEFT + UP)
self.add(formula)
x_scale = 5.0
y_scale = 1.0
probabilities = np.array([1./8, 3./8, 3./8, 1./8])
prob_strings = ["{1\over 8}","{3\over 8}","{3\over 8}","{1\over 8}"]
cumulative_probabilities = np.cumsum(probabilities)
cumulative_probabilities = np.insert(cumulative_probabilities, 0, 0)
print cumulative_probabilities
y_values = np.array([0, 1, 2, 3])
hist = Histogram(probabilities, y_values,
mode = "widths",
x_scale = x_scale,
y_scale = y_scale,
x_labels = "none"
)
flat_hist = Histogram(probabilities, 0 * y_values,
mode = "widths",
x_scale = x_scale,
y_scale = y_scale,
x_labels = "none"
)
self.play(FadeIn(flat_hist))
self.play(
ReplacementTransform(flat_hist, hist)
)
braces = VGroup()
p_labels = VGroup()
# add x labels (braces)
for (p,string,bar) in zip(probabilities, prob_strings,hist.bars):
brace = Brace(bar, DOWN, buff = 0.1)
p_label = TexMobject(string).next_to(brace, DOWN, buff = SMALL_BUFF).scale(0.7)
group = VGroup(brace, p_label)
braces.add(brace)
p_labels.add(p_label)
self.play(
Write(group)
)
labels = VGroup()
for (y, bar) in zip(y_values, hist.bars):
label = TexMobject(str(int(y))).scale(0.7).next_to(bar, UP, buff = SMALL_BUFF)
self.play(FadeIn(label))
labels.add(label)
y_average = np.mean(y_values)
averaged_y_values = y_average * np.ones(np.shape(y_values))
averaged_hist = flat_hist = Histogram(probabilities, averaged_y_values,
mode = "widths",
x_scale = x_scale,
y_scale = y_scale,
x_labels = "none"
).fade(0.2)
ghost_hist = hist.copy().fade(0.8)
labels.fade(0.8)
self.bring_to_back(ghost_hist)
self.play(Transform(hist, averaged_hist))
average_label = TexMobject(str(y_average)).scale(0.7).next_to(averaged_hist, UP, SMALL_BUFF)
one_brace = Brace(averaged_hist, DOWN, buff = 0.1)
one_p_label = TexMobject(str(1)).next_to(one_brace, DOWN, buff = SMALL_BUFF).scale(0.7)
one_group = VGroup(one_brace, one_p_label)
self.play(
FadeIn(average_label),
Transform(braces, one_brace),
Transform(p_labels, one_p_label),
)
class AreaSplitting(Scene):
@ -932,6 +1090,62 @@ class AreaSplitting(Scene):
#self.play(FadeIn(tally))
class DieFace(SVGMobject):
def __init__(self, value, **kwargs):
self.value = value
self.file_name = "Dice-" + str(value)
self.ensure_valid_file()
paths, attributes = svg2paths(self.file_path)
print paths, attributes
SVGMobject.__init__(self, file_name = self.file_name)
# for submob in self.submobject_family():
# if type(submob) == Rectangle:
# submob.set_fill(opacity = 0)
# submob.set_stroke(width = 7)
class RowOfDice(VGroup):
CONFIG = {
"values" : range(1,7)
}
def generate_points(self):
for value in self.values:
new_die = DieFace(value)
new_die.submobjects[0].set_fill(opacity = 0)
new_die.submobjects[0].set_stroke(width = 7)
new_die.next_to(self, RIGHT)
self.add(new_die)
class ShowUncertainty(PiCreatureScene):
def construct(self):
row_of_dice = RowOfDice().scale(0.5).move_to(ORIGIN)
self.add(row_of_dice)
rounded_rect = RoundedRectangle(
width = 3,
height = 2,
corner_radius = 0.1
).shift(3*LEFT)
self.add(rounded_rect)

View File

@ -16,31 +16,44 @@ class Histogram(VMobject):
"end_color" : BLUE,
"x_scale" : 1.0,
"y_scale" : 1.0,
"x_labels" : "auto",
"x_min" : 0
}
def __init__(self, x_values, y_values, **kwargs):
def __init__(self, x_values, y_values, mode = "widths", **kwargs):
# mode = "widths" : x_values means the widths of the bars
# mode = "posts" : x_values means the delimiters btw the bars
digest_config(self, kwargs)
if mode == "widths" and len(x_values) != len(y_values):
raise Exception("Array lengths do not match up!")
elif mode == "posts" and len(x_values) != len(y_values) + 1:
raise Exception("Array lengths do not match up!")
# preliminaries
self.x_values = x_values
self.y_values = y_values
self.y_values = np.array(y_values)
self.x_steps = x_values[1:] - x_values[:-1]
self.x_min = x_values[0] - self.x_steps[0] * 0.5
self.x_posts = (x_values[1:] + x_values[:-1]) * 0.5
self.x_max = x_values[-1] + self.x_steps[-1] * 0.5
self.x_posts = np.insert(self.x_posts,0,self.x_min)
self.x_posts = np.append(self.x_posts,self.x_max)
if mode == "widths":
self.widths = x_values
self.posts = np.cumsum(self.widths)
self.posts = np.insert(self.posts, 0, 0)
self.posts += self.x_min
self.x_max = self.posts[-1]
elif mode == "posts":
self.posts = x_values
self.widths = x_values[1:] - x_values[:-1]
self.x_min = self.posts[0]
self.x_max = self.posts[-1]
else:
raise Exception("Invalid mode or no mode specified!")
self.x_widths = self.x_posts[1:] - self.x_posts[:-1]
self.x_mids = 0.5 * (self.posts[:-1] + self.posts[1:])
self.x_values_scaled = self.x_scale * x_values
self.x_steps_scaled = self.x_scale * self.x_steps
self.x_posts_scaled = self.x_scale * self.x_posts
self.widths_scaled = self.x_scale * self.widths
self.posts_scaled = self.x_scale * self.posts
self.x_min_scaled = self.x_scale * self.x_min
self.x_max_scaled = self.x_scale * self.x_max
self.x_widths_scaled = self.x_scale * self.x_widths
self.y_values_scaled = self.y_scale * self.y_values
@ -50,18 +63,38 @@ class Histogram(VMobject):
def generate_points(self):
def empty_string_array(n):
arr = []
for i in range(n):
arr.append("")
return arr
def num_arr_to_string_arr(arr): # converts number array to string array
ret_arr = []
for x in arr:
ret_arr.append(str(x))
return ret_arr
previous_bar = ORIGIN
self.bars = []
outline_points = []
self.x_labels = text_range(self.x_values[0], self.x_max, self.x_steps[0])
if self.x_labels == "widths":
self.x_labels = num_arr_to_string_arr(self.widths)
elif self.x_labels == "mids":
print self.x_mids
self.x_labels = num_arr_to_string_arr(self.x_mids)
elif self.x_labels == "none":
self.x_labels = empty_string_array(len(self.widths))
for (i,x) in enumerate(self.x_values):
print self.x_labels
for (i,x) in enumerate(self.x_mids):
bar = Rectangle(
width = self.x_widths_scaled[i],
width = self.widths_scaled[i],
height = self.y_values_scaled[i],
)
t = float(x - self.x_values[0])/(self.x_values[-1] - self.x_values[0])
t = float(x - self.x_min)/(self.x_max - self.x_min)
bar_color = interpolate_color(
self.start_color,
self.end_color,
@ -87,7 +120,6 @@ class Histogram(VMobject):
outline_points.append(bar.get_anchors()[1])
previous_bar = bar
# close the outline
# lower right
outline_points.append(bar.get_anchors()[2])
@ -103,6 +135,15 @@ class Histogram(VMobject):
class BuildUpHistogram(Animation):
def __init__(self, hist, **kwargs):
self.histogram = hist
class FlashThroughHistogram(Animation):

View File

@ -717,6 +717,41 @@ class Square(Rectangle):
)
class RoundedRectangle(Rectangle):
CONFIG = {
"corner_radius" : 0.5,
"close_new_points" : True
}
def generate_points(self):
y, x = self.height / 2., self.width / 2.
r = self.corner_radius
arc_ul = ArcBetweenPoints(x * LEFT + (y - r) * UP, (x - r) * LEFT + y * UP, angle = -TAU/4)
arc_ur = ArcBetweenPoints((x - r) * RIGHT + y * UP, x * RIGHT + (y - r) * UP, angle = -TAU/4)
arc_lr = ArcBetweenPoints(x * RIGHT + (y - r) * DOWN, (x - r) * RIGHT + y * DOWN, angle = -TAU/4)
arc_ll = ArcBetweenPoints(x * LEFT + (y - r) * DOWN, (x - r) * LEFT + y * DOWN, angle = TAU/4) # sic! bug in ArcBetweenPoints?
points = arc_ul.points
points = np.append(points,np.array([y * UP]), axis = 0)
points = np.append(points,np.array([y * UP]), axis = 0)
points = np.append(points,arc_ur.points, axis = 0)
points = np.append(points,np.array([x * RIGHT]), axis = 0)
points = np.append(points,np.array([x * RIGHT]), axis = 0)
points = np.append(points,arc_lr.points, axis = 0)
points = np.append(points,np.array([y * DOWN]), axis = 0)
points = np.append(points,np.array([y * DOWN]), axis = 0)
points = np.append(points,arc_ll.points[::-1], axis = 0) # sic! see comment above
points = np.append(points,np.array([x * LEFT]), axis = 0)
points = np.append(points,np.array([x * LEFT]), axis = 0)
points = np.append(points,np.array([x * LEFT + (y - r) * UP]), axis = 0)
points = points[::-1]
self.set_points(points)
class Grid(VMobject):
CONFIG = {
"height": 6.0,

View File

@ -4,10 +4,12 @@ import string
import warnings
from xml.dom import minidom
from utils.color import *
from constants import *
from mobject.geometry import Circle
from mobject.geometry import Rectangle
from mobject.geometry import RoundedRectangle
from utils.bezier import is_closed
from utils.config_ops import digest_config
from utils.config_ops import digest_locals
@ -34,7 +36,7 @@ class SVGMobject(VMobject):
"file_name": None,
"unpack_groups": True, # if False, creates a hierarchy of VGroups
"stroke_width": 0,
"fill_opacity": 1,
"fill_opacity": 1.0,
# "fill_color" : LIGHT_GREY,
"propagate_style_to_family": True,
}
@ -155,16 +157,54 @@ class SVGMobject(VMobject):
return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN)
def rect_to_mobject(self, rect_element):
if rect_element.hasAttribute("fill"):
if Color(str(rect_element.getAttribute("fill"))) == Color(WHITE):
fill_color = rect_element.getAttribute("fill")
stroke_color = rect_element.getAttribute("stroke")
stroke_width = rect_element.getAttribute("stroke-width")
corner_radius = rect_element.getAttribute("rx")
# input preprocessing
if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE):
opacity = 0
fill_color = BLACK # shdn't be necessary but avoids error msgs
if fill_color in ["#000", "#000000"]:
fill_color = WHITE
if stroke_color in ["", "none", "#FFF", "#FFFFFF"] or Color(stroke_color) == Color(WHITE):
stroke_width = 0
stroke_color = BLACK
if stroke_color in ["#000", "#000000"]:
stroke_color = WHITE
if stroke_width in ["", "none", "0"]:
stroke_width = 0
# is there sth to draw?
if opacity == 0 and stroke_width == 0:
return
if corner_radius in ["", "0", "none"]:
corner_radius = 0
corner_radius = float(corner_radius)
if corner_radius == 0:
mob = Rectangle(
width=float(rect_element.getAttribute("width")),
height=float(rect_element.getAttribute("height")),
stroke_width=0,
fill_color=WHITE,
fill_opacity=1.0
width = float(rect_element.getAttribute("width")),
height = float(rect_element.getAttribute("height")),
stroke_width = stroke_width,
stroke_color = stroke_color,
fill_color = fill_color,
fill_opacity = opacity
)
else:
mob = RoundedRectangle(
width = float(rect_element.getAttribute("width")),
height = float(rect_element.getAttribute("height")),
stroke_width = stroke_width,
stroke_color = stroke_color,
fill_color = fill_color,
fill_opacity = opacity,
corner_radius = corner_radius
)
mob.shift(mob.get_center() - mob.get_corner(UP + LEFT))
return mob

View File

@ -4,12 +4,13 @@ from constants import *
import itertools as it
from scene.scene import Scene
from animation.creation import Write
from animation.creation import Write, DrawBorderThenFill, ShowCreation
from animation.transform import Transform
from animation.update import UpdateFromAlphaFunc
from mobject.functions import ParametricFunction
from mobject.geometry import Line
from mobject.geometry import Rectangle
from mobject.geometry import RegularPolygon
from mobject.number_line import NumberLine
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
@ -49,6 +50,8 @@ class GraphScene(Scene):
"default_input_color": YELLOW,
"default_riemann_start_color": BLUE,
"default_riemann_end_color": GREEN,
"area_opacity" : 0.8,
"num_rects" : 50,
}
def setup(self):
@ -245,6 +248,8 @@ class GraphScene(Scene):
sample_input = x
elif input_sample_type == "right":
sample_input = x + dx
elif input_sample_type == "center":
sample_input = x + 0.5 * dx
else:
raise Exception("Invalid input sample type")
graph_point = self.input_to_graph_point(sample_input, graph)
@ -284,6 +289,18 @@ class GraphScene(Scene):
for n in range(n_iterations)
]
def get_area(self, graph, t_min, t_max):
numerator = max(t_max - t_min, 0.01)
dx = float(numerator) / self.num_rects
return self.get_riemann_rectangles(
graph,
x_min = t_min,
x_max = t_max,
dx = dx,
stroke_width = 0,
).set_fill(opacity = self.area_opacity)
def transform_between_riemann_rects(self, curr_rects, new_rects, **kwargs):
transform_kwargs = {
"run_time": 2,
@ -421,6 +438,84 @@ class GraphScene(Scene):
return group
def add_T_label(self, x_val, color = WHITE, animated = False, **kwargs):
triangle = RegularPolygon(n=3, start_angle = np.pi/2)
triangle.scale_to_fit_height(MED_SMALL_BUFF)
triangle.move_to(self.coords_to_point(x_val, 0), UP)
triangle.set_fill(color, 1)
triangle.set_stroke(width = 0)
T_label = TexMobject(self.variable_point_label, fill_color = color)
T_label.next_to(triangle, DOWN)
v_line = self.get_vertical_line_to_graph(
x_val, self.v_graph,
color = YELLOW
)
if animated:
self.play(
DrawBorderThenFill(triangle),
ShowCreation(v_line),
Write(T_label, run_time = 1),
**kwargs
)
else:
self.add(triangle, v_line, T_label)
self.T_label_group = VGroup(T_label, triangle)
self.right_v_line = v_line
def get_animation_integral_bounds_change(
self,
graph,
new_t_min,
new_t_max,
run_time = 1.0
):
curr_t_min = self.x_axis.point_to_number(self.area.get_left())
curr_t_max = self.x_axis.point_to_number(self.area.get_right())
if new_t_min is None:
new_t_min = curr_t_min
if new_t_max is None:
new_t_max = curr_t_max
group = VGroup(self.area)
if hasattr(self, "right_v_line"):
group.add(self.right_v_line)
else:
group.add(VGroup())
# because update_group expects 3 elements in group
if hasattr(self, "T_label_group"):
group.add(self.T_label_group)
else:
group.add(VGroup())
def update_group(group, alpha):
area, v_line, T_label = group
t_min = interpolate(curr_t_min, new_t_min, alpha)
t_max = interpolate(curr_t_max, new_t_max, alpha)
new_area = self.get_area(graph,t_min, t_max)
new_v_line = self.get_vertical_line_to_graph(
t_max, graph
)
new_v_line.set_color(v_line.get_color())
T_label.move_to(new_v_line.get_bottom(), UP)
#Fade close to 0
if len(T_label) > 0:
T_label[0].set_fill(opacity = min(1, t_max))
Transform(area, new_area).update(1)
Transform(v_line, new_v_line).update(1)
return group
return UpdateFromAlphaFunc(group, update_group, run_time = run_time)
def animate_secant_slope_group_change(
self, secant_slope_group,
target_dx=None,
@ -462,3 +557,23 @@ class GraphScene(Scene):
)
secant_slope_group.kwargs["x"] = target_x
secant_slope_group.kwargs["dx"] = target_dx