manimの作法 その53

#概要

manimの作法、調べてみた。
複素数、使ってみた。

#サンプルコード

```
from manimlib.imports import *

class FourierCirclesScene(Scene):
	CONFIG = {
		"n_vectors": 10,
		"big_radius": 2,
		"colors": [BLUE_D, BLUE_C, BLUE_E, GREY_BROWN, ],
		"circle_style": {
			"stroke_width": 2,
		},
		"vector_config": {
			"buff": 0,
			"max_tip_length_to_length_ratio": 0.35,
			"tip_length": 0.15,
			"max_stroke_width_to_length_ratio": 10,
			"stroke_width": 2,
		},
		"circle_config": {
			"stroke_width": 1,
		},
		"base_frequency": 1,
		"slow_factor": 0.25,
		"center_point": ORIGIN,
		"parametric_function_step_size": 0.001,
		"drawn_path_color": YELLOW,
		"drawn_path_stroke_width": 2,
	}
	def setup(self):
		self.slow_factor_tracker = ValueTracker(self.slow_factor)
		self.vector_clock = ValueTracker(0)
		self.vector_clock.add_updater(lambda m, dt: m.increment_value(self.get_slow_factor() * dt))
		self.add(self.vector_clock)
	def get_slow_factor(self):
		return self.slow_factor_tracker.get_value()
	def get_vector_time(self):
		return self.vector_clock.get_value()
	def get_freqs(self):
		n = self.n_vectors
		all_freqs = list(range(n // 2, -n // 2, -1))
		all_freqs.sort(key = abs)
		return all_freqs
	def get_coefficients(self):
		return [complex(0) for x in range(self.n_vectors)]
	def get_color_iterator(self):
		return it.cycle(self.colors)
	def get_rotating_vectors(self, freqs = None, coefficients = None):
		vectors = VGroup()
		self.center_tracker = VectorizedPoint(self.center_point)
		if freqs is None:
			freqs = self.get_freqs()
		if coefficients is None:
			coefficients = self.get_coefficients()
		last_vector = None
		for freq, coefficient in zip(freqs, coefficients):
			if last_vector:
				center_func = last_vector.get_end
			else:
				center_func = self.center_tracker.get_location
			vector = self.get_rotating_vector(coefficient = coefficient, freq = freq, center_func = center_func, )
			vectors.add(vector)
			last_vector = vector
		return vectors
	def get_rotating_vector(self, coefficient, freq, center_func):
		vector = Vector(RIGHT, **self.vector_config)
		vector.scale(abs(coefficient))
		if abs(coefficient) == 0:
			phase = 0
		else:
			phase = np.log(coefficient).imag
		vector.rotate(phase, about_point = ORIGIN)
		vector.freq = freq
		vector.coefficient = coefficient
		vector.center_func = center_func
		vector.add_updater(self.update_vector)
		return vector
	def update_vector(self, vector, dt):
		time = self.get_vector_time()
		coef = vector.coefficient
		freq = vector.freq
		phase = np.log(coef).imag
		vector.set_length(abs(coef))
		vector.set_angle(phase + time * freq * TAU)
		vector.shift(vector.center_func() - vector.get_start())
		return vector
	def get_circles(self, vectors):
		return VGroup(*[self.get_circle(vector, color=color) for vector, color in zip(vectors, self.get_color_iterator())])
	def get_circle(self, vector, color = BLUE):
		circle = Circle(color=color, **self.circle_config)
		circle.center_func = vector.get_start
		circle.radius_func = vector.get_length
		circle.add_updater(self.update_circle)
		return circle
	def update_circle(self, circle):
		circle.set_width(2 * circle.radius_func())
		circle.move_to(circle.center_func())
		return circle
	def get_vector_sum_path(self, vectors, color = YELLOW):
		coefs = [v.coefficient for v in vectors]
		freqs = [v.freq for v in vectors]
		center = vectors[0].get_start()
		path = ParametricFunction(lambda t: center + reduce(op.add, [complex_to_R3(coef * np.exp(TAU * 1j * freq * t)) for coef, freq in zip(coefs, freqs)]), t_min=0, t_max=1, color=color, step_size=self.parametric_function_step_size, )
		return path
	def get_drawn_path_alpha(self):
		return self.get_vector_time()
	def get_drawn_path(self, vectors, stroke_width = None, **kwargs):
		if stroke_width is None:
			stroke_width = self.drawn_path_stroke_width
		path = self.get_vector_sum_path(vectors, **kwargs)
		broken_path = CurvesAsSubmobjects(path)
		broken_path.curr_time = 0
		def update_path(path, dt):
			alpha = self.get_drawn_path_alpha()
			n_curves = len(path)
			for a, sp in zip(np.linspace(0, 1, n_curves), path):
				b = alpha - a
				if b < 0:
					width = 0
				else:
					width = stroke_width * (1 - (b % 1))
				sp.set_stroke(width = width)
			path.curr_time += dt
			return path
		broken_path.set_color(self.drawn_path_color)
		broken_path.add_updater(update_path)
		return broken_path
	def get_y_component_wave(self, vectors, left_x = 1, color = PINK, n_copies = 2, right_shift_rate = 5):
		path = self.get_vector_sum_path(vectors)
		wave = ParametricFunction(lambda t: op.add(right_shift_rate * t * LEFT, path.function(t)[1] * UP), t_min = path.t_min, t_max = path.t_max, color = color, )
		wave_copies = VGroup(*[wave.copy() for x in range(n_copies)])
		wave_copies.arrange(RIGHT, buff = 0)
		top_point = wave_copies.get_top()
		wave.creation = ShowCreation(wave, run_time = (1 / self.get_slow_factor()), rate_func = linear, )
		cycle_animation(wave.creation)
		wave.add_updater(lambda m: m.shift((m.get_left()[0] - left_x) * LEFT))
		def update_wave_copies(wcs):
			index = int(wave.creation.total_time * self.get_slow_factor())
			wcs[:index].match_style(wave)
			wcs[index:].set_stroke(width = 0)
			wcs.next_to(wave, RIGHT, buff = 0)
			wcs.align_to(top_point, UP)
		wave_copies.add_updater(update_wave_copies)
		return VGroup(wave, wave_copies)
	def get_wave_y_line(self, vectors, wave):
		return DashedLine(vectors[-1].get_end(), wave[0].get_end(), stroke_width = 1, dash_length = DEFAULT_DASH_LENGTH * 0.5, )
	def get_coefficients_of_path(self, path, n_samples = 10000, freqs = None):
		if freqs is None:
			freqs = self.get_freqs()
		dt = 1 / n_samples
		ts = np.arange(0, 1, dt)
		samples = np.array([path.point_from_proportion(t) for t in ts])
		samples -= self.center_point
		complex_samples = samples[:, 0] + 1j * samples[:, 1]
		result = []
		for freq in freqs:
			riemann_sum = np.array([np.exp(-TAU * 1j * freq * t) * cs for t, cs in zip(ts, complex_samples)]).sum() * dt
			result.append(riemann_sum)
		return result

class test(FourierCirclesScene):
	CONFIG = {
		"n_vectors": 51,
		"center_point": ORIGIN,
		"slow_factor": 0.1,
		"n_cycles": 2,
		"tex": "\\pi",
		"start_drawn": False,
		"max_circle_stroke_width": 1,
	}
	def construct(self):
		self.add_vectors_circles_path()
		for n in range(self.n_cycles):
			self.run_one_cycle()
	def add_vectors_circles_path(self):
		path = self.get_path()
		coefs = self.get_coefficients_of_path(path)
		vectors = self.get_rotating_vectors(coefficients = coefs)
		circles = self.get_circles(vectors)
		self.set_decreasing_stroke_widths(circles)
		drawn_path = self.get_drawn_path(vectors)
		if self.start_drawn:
			self.vector_clock.increment_value(1)
		self.add(path)
		self.add(vectors)
		self.add(circles)
		self.add(drawn_path)
		self.vectors = vectors
		self.circles = circles
		self.path = path
		self.drawn_path = drawn_path
	def run_one_cycle(self):
		time = 1 / self.slow_factor
		self.wait(time)
	def set_decreasing_stroke_widths(self, circles):
		mcsw = self.max_circle_stroke_width
		for k, circle in zip(it.count(1), circles):
			circle.set_stroke(width=max(mcsw / k, mcsw, ))
		return circles
	def get_path(self):
		tex_mob = TexMobject(self.tex)
		tex_mob.set_height(6)
		path = tex_mob.family_members_with_points()[0]
		path.set_fill(opacity = 0)
		path.set_stroke(WHITE, 1)
		return path





```

#生成した動画

https://www.youtube.com/watch?v=vT2_ov9NFBQ

以上。