Merge pull request #129 from 3b1b/uncertainty

Uncertainty
This commit is contained in:
Grant Sanderson 2018-02-16 12:16:59 -08:00 committed by GitHub
commit d53412d738
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1511 additions and 201 deletions

View file

@ -32,6 +32,7 @@ from topics.graph_scene import *
USE_ALMOST_FOURIER_BY_DEFAULT = True
NUM_SAMPLES_FOR_FFT = 1000
DEFAULT_COMPLEX_TO_REAL_FUNC = lambda z : z.real
def get_fourier_graph(
@ -53,7 +54,7 @@ def get_fourier_graph(
graph = VMobject()
graph.set_points_smoothly([
axes.coords_to_point(
x, 200.0*complex_to_real_func(y)/n_samples,
x, complex_to_real_func(y)/n_samples,
)
for x, y in zip(frequencies, fft_output[:n_samples//2])
])
@ -62,18 +63,17 @@ def get_fourier_graph(
def get_fourier_transform(
func, t_min, t_max,
complex_to_real_func = lambda z : z.real,
complex_to_real_func = DEFAULT_COMPLEX_TO_REAL_FUNC,
use_almost_fourier = USE_ALMOST_FOURIER_BY_DEFAULT,
**kwargs ##Just eats these
):
scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0
def fourier_transform(f):
return scalar*scipy.integrate.quad(
lambda t : complex_to_real_func(
# f(t) e^{-TAU*i*f*t}
func(t)*np.exp(complex(0, -TAU*f*t))
),
z = scalar*scipy.integrate.quad(
lambda t : func(t)*np.exp(complex(0, -TAU*f*t)),
t_min, t_max
)[0]
return complex_to_real_func(z)
return fourier_transform
##
@ -939,11 +939,28 @@ class FourierMachineScene(Scene):
if not hasattr(self, "frequency_axes"):
self.get_frequency_axes()
func = time_graph.underlying_function
t_min = self.time_axes.x_min
t_max = self.time_axes.x_max
t_axis = self.time_axes.x_axis
t_min = t_axis.point_to_number(time_graph.points[0])
t_max = t_axis.point_to_number(time_graph.points[-1])
f_max = self.frequency_axes.x_max
# result = get_fourier_graph(
# self.frequency_axes, func, t_min, t_max,
# **kwargs
# )
# too_far_right_point_indices = [
# i
# for i, point in enumerate(result.points)
# if self.frequency_axes.x_axis.point_to_number(point) > f_max
# ]
# if too_far_right_point_indices:
# i = min(too_far_right_point_indices)
# prop = float(i)/len(result.points)
# result.pointwise_become_partial(result, 0, prop)
# return result
return self.frequency_axes.get_graph(
get_fourier_transform(func, t_min, t_max, **kwargs),
color = self.center_of_mass_color,
**kwargs
)
def get_polarized_mobject(self, mobject, freq = 1.0):
@ -999,7 +1016,8 @@ class FourierMachineScene(Scene):
vector = Vector(UP, color = WHITE)
graph_copy = graph.copy()
x_axis = self.time_axes.x_axis
x_min, x_max = x_axis.x_min, x_axis.x_max
x_min = x_axis.point_to_number(graph.points[0])
x_max = x_axis.point_to_number(graph.points[-1])
def update_vector(vector, alpha):
x = interpolate(x_min, x_max, alpha)
vector.put_start_and_end_on(
@ -1016,7 +1034,13 @@ class FourierMachineScene(Scene):
origin = self.circle_plane.coords_to_point(0, 0)
graph_copy = polarized_graph.copy()
def update_vector(vector, alpha):
point = graph_copy.point_from_proportion(alpha)
# Not sure why this is needed, but without smoothing
# out the alpha like this, the vector would occasionally
# jump around
point = center_of_mass([
graph_copy.point_from_proportion(alpha+d)
for d in np.linspace(-0.001, 0.001, 5)
])
vector.put_start_and_end_on_with_projection(origin, point)
return vector
return UpdateFromAlphaFunc(vector, update_vector, **config)
@ -1299,6 +1323,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
CONFIG = {
"initial_winding_frequency" : 3.0,
"center_of_mass_color" : RED,
"center_of_mass_multiple" : 1,
}
def construct(self):
self.remove(self.pi_creature)
@ -1581,10 +1606,11 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
def get_pol_graph_center_of_mass(self):
pg = self.graph.polarized_mobject
result = center_of_mass([
pg.point_from_proportion(alpha)
for alpha in np.linspace(0, 1, 1000)
])
result = center_of_mass(pg.get_anchors())
if self.center_of_mass_multiple != 1:
mult = self.center_of_mass_multiple
origin = self.circle_plane.coords_to_point(0, 0)
result = mult*(result - origin) + origin
return result
def generate_fourier_dot_transform(self, fourier_graph):
@ -1626,7 +1652,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
def change_frequency(self, new_freq, **kwargs):
kwargs["run_time"] = kwargs.get("run_time", 3)
kwargs["rate_func"] = kwargs.get(
rate_func = kwargs.pop(
"rate_func", bezier([0, 0, 1, 1])
)
added_anims = kwargs.get("added_anims", [])
@ -1645,6 +1671,8 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
anims.append(self.fourier_graph_dot_anim)
if hasattr(self, "fourier_graph_drawing_update_anim"):
anims.append(self.fourier_graph_drawing_update_anim)
for anim in anims:
anim.rate_func = rate_func
anims += added_anims
self.play(*anims, **kwargs)

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ class Camera(object):
"max_allowable_norm" : 2*SPACE_WIDTH,
"image_mode" : "RGBA",
"n_rgb_coords" : 4,
"background_alpha" : 0, #Out of color_max_val
"background_alpha" : 0, #Out of rgb_max_val
"pixel_array_dtype" : 'uint8',
"use_z_coordinate_for_display_order" : False,
# z_buff_func is only used if the flag above is set to True.
@ -40,7 +40,7 @@ class Camera(object):
def __init__(self, background = None, **kwargs):
digest_config(self, kwargs, locals())
self.color_max_val = np.iinfo(self.pixel_array_dtype).max
self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max
self.init_background()
self.resize_space_shape()
self.reset()
@ -92,7 +92,7 @@ class Camera(object):
retval = np.array(pixel_array)
if convert_from_floats:
retval = np.apply_along_axis(
lambda f : (f * self.color_max_val).astype(self.pixel_array_dtype),
lambda f : (f * self.rgb_max_val).astype(self.pixel_array_dtype),
2,
retval)
return retval
@ -177,29 +177,30 @@ class Camera(object):
def capture_mobjects(self, mobjects, **kwargs):
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
vmobjects = []
for mobject in mobjects:
if isinstance(mobject, VMobject):
vmobjects.append(mobject)
elif len(vmobjects) > 0:
self.display_multiple_vectorized_mobjects(vmobjects)
vmobjects = []
if isinstance(mobject, PMobject):
self.display_point_cloud(
mobject.points, mobject.rgbas,
self.adjusted_thickness(mobject.stroke_width)
)
elif isinstance(mobject, ImageMobject):
self.display_image_mobject(mobject)
elif isinstance(mobject, Mobject):
pass #Remainder of loop will handle submobjects
else:
raise Exception(
"Unknown mobject type: " + mobject.__class__.__name__
)
#TODO, more? Call out if it's unknown?
self.display_multiple_vectorized_mobjects(vmobjects)
# Organize this list into batches of the same type, and
# apply corresponding function to those batches
type_func_pairs = [
(VMobject, self.display_multiple_vectorized_mobjects),
(PMobject, self.display_multiple_point_cloud_mobjects),
(ImageMobject, self.display_multiple_image_mobjects),
(Mobject, lambda batch : batch), #Do nothing
]
def get_mobject_type(mobject):
for mobject_type, func in type_func_pairs:
if isinstance(mobject, mobject_type):
return mobject_type
raise Exception(
"Trying to display something which is not of type Mobject"
)
batch_type_pairs = batch_by_property(mobjects, get_mobject_type)
#Display in these batches
for batch, batch_type in batch_type_pairs:
#check what the type is, and call the appropriate function
for mobject_type, func in type_func_pairs:
if batch_type == mobject_type:
func(batch)
## Methods associated with svg rendering
@ -215,12 +216,12 @@ class Camera(object):
def display_multiple_vectorized_mobjects(self, vmobjects):
if len(vmobjects) == 0:
return
batches = batch_by_property(
batch_file_pairs = batch_by_property(
vmobjects,
lambda vm : vm.get_background_image_file()
)
for batch in batches:
if batch[0].get_background_image_file():
for batch, file_name in batch_file_pairs:
if file_name:
self.display_multiple_background_colored_vmobject(batch)
else:
self.display_multiple_non_background_colored_vmobjects(batch)
@ -252,7 +253,7 @@ class Camera(object):
stroke_hex = rgb_to_hex(stroke_rgb)
pen = aggdraw.Pen(stroke_hex, stroke_width)
fill_opacity = int(self.color_max_val*vmobject.get_fill_opacity())
fill_opacity = int(self.rgb_max_val*vmobject.get_fill_opacity())
if fill_opacity == 0:
fill = None
else:
@ -305,13 +306,19 @@ class Camera(object):
def display_multiple_background_colored_vmobject(self, cvmobjects):
displayer = self.get_background_colored_vmobject_displayer()
cvmobject_pixel_array = displayer.display(*cvmobjects)
self.pixel_array[:,:] = np.maximum(
self.pixel_array, cvmobject_pixel_array
)
self.overlay_rgba_array(cvmobject_pixel_array)
return self
## Methods for other rendering
def display_multiple_point_cloud_mobjects(self, pmobjects):
for pmobject in pmobjects:
self.display_point_cloud(
pmobject.points,
pmobject.rgbas,
self.adjusted_thickness(pmobject.stroke_width)
)
def display_point_cloud(self, points, rgbas, thickness):
if len(points) == 0:
return
@ -322,7 +329,7 @@ class Camera(object):
)
rgba_len = self.pixel_array.shape[2]
rgbas = (self.color_max_val*rgbas).astype(self.pixel_array_dtype)
rgbas = (self.rgb_max_val*rgbas).astype(self.pixel_array_dtype)
target_len = len(pixel_coords)
factor = target_len/len(rgbas)
rgbas = np.array([rgbas]*factor).reshape((target_len, rgba_len))
@ -342,6 +349,10 @@ class Camera(object):
new_pa[indices] = rgbas
self.pixel_array = new_pa.reshape((ph, pw, rgba_len))
def display_multiple_image_mobjects(self, image_mobjects):
for image_mobject in image_mobjects:
self.display_image_mobject(image_mobject)
def display_image_mobject(self, image_mobject):
corner_coords = self.points_to_pixel_coords(image_mobject.points)
ul_coords, ur_coords, dl_coords = corner_coords
@ -410,17 +421,23 @@ class Camera(object):
self.overlay_rgba_array(image)
def overlay_rgba_array(self, arr):
# """ Overlays arr onto self.pixel_array with relevant alphas"""
bg, fg = fdiv(self.pixel_array, self.color_max_val), fdiv(arr, self.color_max_val)
bga, fga = [arr[:,:,3:] for arr in bg, fg]
alpha_sum = fga + (1-fga)*bga
with np.errstate(divide = 'ignore', invalid='ignore'):
bg[:,:,:3] = reduce(op.add, [
np.divide(fg[:,:,:3]*fga, alpha_sum),
np.divide(bg[:,:,:3]*(1-fga)*bga, alpha_sum),
])
bg[:,:,3:] = 1 - (1 - bga)*(1 - fga)
self.pixel_array = (self.color_max_val*bg).astype(self.pixel_array_dtype)
fg = arr
bg = self.pixel_array
# rgba_max_val = self.rgb_max_val
src_rgb, src_a, dst_rgb, dst_a = [
a.astype(np.float32)/self.rgb_max_val
for a in fg[...,:3], fg[...,3], bg[...,:3], bg[...,3]
]
out_a = src_a + dst_a*(1.0-src_a)
out_rgb = fdiv(
src_rgb*src_a[..., None] + \
dst_rgb*dst_a[..., None]*(1.0-src_a[..., None]),
out_a[..., None]
)
self.pixel_array[..., :3] = out_rgb*self.rgb_max_val
self.pixel_array[..., 3] = out_a*self.rgb_max_val
def align_points_to_camera(self, points):
## This is where projection should live
@ -537,8 +554,7 @@ class BackgroundColoredVMobjectDisplayer(object):
mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
return self.resize_background_array(background_array, width, height, mode)
def get_background_array(self, cvmobject):
file_name = cvmobject.get_background_image_file()
def get_background_array(self, file_name):
if file_name in self.file_name_to_pixel_array_map:
return self.file_name_to_pixel_array_map[file_name]
full_path = get_full_raster_image_path(file_name)
@ -553,12 +569,12 @@ class BackgroundColoredVMobjectDisplayer(object):
return array
def display(self, *cvmobjects):
batches = batch_by_property(
batch_image_file_pairs = batch_by_property(
cvmobjects, lambda cv : cv.get_background_image_file()
)
curr_array = None
for batch in batches:
background_array = self.get_background_array(batch[0])
for batch, image_file in batch_image_file_pairs:
background_array = self.get_background_array(image_file)
for cvmobject in batch:
self.camera.display_vectorized(cvmobject, self.canvas)
self.canvas.flush()

View file

@ -227,22 +227,30 @@ def adjacent_pairs(objects):
return zip(objects, list(objects[1:])+[objects[0]])
def batch_by_property(items, property_func):
batches = []
def add_batch(batch):
"""
Takes in a list, and returns a list of tuples, (batch, prop)
such that all items in a batch have the same output when
put into property_func, and such that chaining all these
batches together would give the original list.
"""
batch_prop_pairs = []
def add_batch_prop_pair(batch):
if len(batch) > 0:
batches.append(batch)
batch_prop_pairs.append(
(batch, property_func(batch[0]))
)
curr_batch = []
curr_prop = None
for item in items:
prop = property_func(item)
if prop != curr_prop:
add_batch(curr_batch)
add_batch_prop_pair(curr_batch)
curr_prop = prop
curr_batch = [item]
else:
curr_batch.append(item)
add_batch(curr_batch)
return batches
add_batch_prop_pair(curr_batch)
return batch_prop_pairs
def complex_to_R3(complex_num):
return np.array((complex_num.real, complex_num.imag, 0))

View file

@ -141,9 +141,15 @@ class TexMobject(SVGMobject):
self.submobjects = new_submobjects
return self
def get_parts_by_tex(self, tex, substring = True):
def get_parts_by_tex(self, tex, substring = True, case_sensitive = True):
def test(tex1, tex2):
return tex1 == tex2 or (substring and tex1 in tex2)
if not case_sensitive:
tex1 = tex1.lower()
tex2 = tex2.lower()
if substring:
return tex1 in tex2
else:
return tex1 == tex2
tex_submobjects = filter(
lambda m : isinstance(m, TexMobject),

View file

@ -122,7 +122,7 @@ class VMobject(Mobject):
return self
def get_fill_rgb(self):
return self.fill_rgb
return np.clip(self.fill_rgb, 0, 1)
def get_fill_color(self):
try:
@ -135,7 +135,7 @@ class VMobject(Mobject):
return np.clip(self.fill_opacity, 0, 1)
def get_stroke_rgb(self):
return self.stroke_rgb
return np.clip(self.stroke_rgb, 0, 1)
def get_stroke_color(self):
try:

View file

@ -35,90 +35,6 @@ VELOCITY_COLOR = GREEN
#### Warning, scenes here not updated based on most recent GraphScene changes #######
class Car(SVGMobject):
CONFIG = {
"file_name" : "Car",
"height" : 1,
"color" : "#BBBBBB",
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_height(self.height)
self.set_stroke(color = WHITE, width = 0)
self.set_fill(self.color, opacity = 1)
randy = Randolph(mode = "happy")
randy.scale_to_fit_height(0.6*self.get_height())
randy.stretch(0.8, 0)
randy.look(RIGHT)
randy.move_to(self)
randy.shift(0.07*self.height*(RIGHT+UP))
self.randy = randy
self.add_to_back(randy)
orientation_line = Line(self.get_left(), self.get_right())
orientation_line.set_stroke(width = 0)
self.add(orientation_line)
self.orientation_line = orientation_line
self.add_treds_to_tires()
def move_to(self, point_or_mobject):
vect = rotate_vector(
UP+LEFT, self.orientation_line.get_angle()
)
self.next_to(point_or_mobject, vect, buff = 0)
return self
def get_front_line(self):
return DashedLine(
self.get_corner(UP+RIGHT),
self.get_corner(DOWN+RIGHT),
color = DISTANCE_COLOR,
dashed_segment_length = 0.05,
)
def add_treds_to_tires(self):
for tire in self.get_tires():
radius = tire.get_width()/2
center = tire.get_center()
tred = Line(
0.9*radius*RIGHT, 1.4*radius*RIGHT,
stroke_width = 2,
color = BLACK
)
tred.rotate_in_place(np.pi/4)
for theta in np.arange(0, 2*np.pi, np.pi/4):
new_tred = tred.copy()
new_tred.rotate(theta)
new_tred.shift(center)
tire.add(new_tred)
return self
def get_tires(self):
return VGroup(self[1][1], self[1][3])
class MoveCar(ApplyMethod):
CONFIG = {
"moving_forward" : True,
}
def __init__(self, car, target_point, **kwargs):
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
distance = np.linalg.norm(displacement)
if not self.moving_forward:
distance *= -1
tire_radius = car.get_tires()[0].get_width()/2
self.total_tire_radians = -distance/tire_radius
def update_mobject(self, alpha):
ApplyMethod.update_mobject(self, alpha)
if alpha == 0:
return
radians = alpha*self.total_tire_radians
for tire in self.mobject.get_tires():
tire.rotate_in_place(radians)
class IncrementNumber(Succession):
CONFIG = {
"start_num" : 0,

View file

@ -219,7 +219,29 @@ class Axes(VGroup):
return graph
def input_to_graph_point(self, x, graph):
return self.coords_to_point(x, graph.underlying_function(x))
if hasattr(graph, "underlying_function"):
return self.coords_to_point(x, graph.underlying_function(x))
else:
#binary search
lh, rh = 0, 1
while abs(lh - rh) > 0.001:
mh = np.mean([lh, rh])
hands = [lh, mh, rh]
points = map(graph.point_from_proportion, hands)
lx, mx, rx = map(self.x_axis.point_to_number, points)
if lx <= x and rx >= x:
if mx > x:
rh = mh
else:
lh = mh
elif lx <= x and rx <= x:
return points[2]
elif lx >= x and rx >= x:
return points[0]
elif lx > x and rx < x:
lh, rh = rh, lh
return points[1]
class ThreeDAxes(Axes):
CONFIG = {

View file

@ -486,6 +486,104 @@ class ThoughtBubble(Bubble):
self.submobjects[-1].set_fill(GREEN_SCREEN, opacity = 1)
return self
class Car(SVGMobject):
CONFIG = {
"file_name" : "Car",
"height" : 1,
"color" : LIGHT_GREY,
"light_colors" : [BLACK, BLACK],
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_height(self.height)
self.set_stroke(color = WHITE, width = 0)
self.set_fill(self.color, opacity = 1)
from topics.characters import Randolph
randy = Randolph(mode = "happy")
randy.scale_to_fit_height(0.6*self.get_height())
randy.stretch(0.8, 0)
randy.look(RIGHT)
randy.move_to(self)
randy.shift(0.07*self.height*(RIGHT+UP))
self.randy = self.pi_creature = randy
self.add_to_back(randy)
orientation_line = Line(self.get_left(), self.get_right())
orientation_line.set_stroke(width = 0)
self.add(orientation_line)
self.orientation_line = orientation_line
for light, color in zip(self.get_lights(), self.light_colors):
light.set_fill(color, 1)
light.is_subpath = False
self.add_treds_to_tires()
def move_to(self, point_or_mobject):
vect = rotate_vector(
UP+LEFT, self.orientation_line.get_angle()
)
self.next_to(point_or_mobject, vect, buff = 0)
return self
def get_front_line(self):
return DashedLine(
self.get_corner(UP+RIGHT),
self.get_corner(DOWN+RIGHT),
color = DISTANCE_COLOR,
dashed_segment_length = 0.05,
)
def add_treds_to_tires(self):
for tire in self.get_tires():
radius = tire.get_width()/2
center = tire.get_center()
tred = Line(
0.9*radius*RIGHT, 1.4*radius*RIGHT,
stroke_width = 2,
color = BLACK
)
tred.rotate_in_place(np.pi/4)
for theta in np.arange(0, 2*np.pi, np.pi/4):
new_tred = tred.copy()
new_tred.rotate(theta, about_point = ORIGIN)
new_tred.shift(center)
tire.add(new_tred)
return self
def get_tires(self):
return VGroup(self[1][1], self[1][3])
def get_lights(self):
return VGroup(self.get_front_light(), self.get_rear_light())
def get_front_light(self):
return self[1][5]
def get_rear_light(self):
return self[1][8]
class MoveCar(ApplyMethod):
CONFIG = {
"moving_forward" : True,
}
def __init__(self, car, target_point, **kwargs):
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
distance = np.linalg.norm(displacement)
if not self.moving_forward:
distance *= -1
tire_radius = car.get_tires()[0].get_width()/2
self.total_tire_radians = -distance/tire_radius
def update_mobject(self, alpha):
ApplyMethod.update_mobject(self, alpha)
if alpha == 0:
return
radians = alpha*self.total_tire_radians
for tire in self.mobject.get_tires():
tire.rotate_in_place(radians)
#TODO: Where should this live?
class Broadcast(LaggedStart):