Merge pull request #1952 from 3b1b/video-work

More miscellaneous tweaks and fixes
This commit is contained in:
Grant Sanderson 2022-12-29 15:53:02 -08:00 committed by GitHub
commit 124c83d94e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 291 additions and 302 deletions

View file

@ -29,6 +29,7 @@ class AnimationGroup(Animation):
run_time: float = -1, # If negative, default to sum of inputed animation runtimes run_time: float = -1, # If negative, default to sum of inputed animation runtimes
lag_ratio: float = 0.0, lag_ratio: float = 0.0,
group: Mobject | None = None, group: Mobject | None = None,
group_type: type = Group,
**kwargs **kwargs
): ):
self.animations = [prepare_animation(anim) for anim in animations] self.animations = [prepare_animation(anim) for anim in animations]
@ -38,7 +39,7 @@ class AnimationGroup(Animation):
self.lag_ratio = lag_ratio self.lag_ratio = lag_ratio
self.group = group self.group = group
if self.group is None: if self.group is None:
self.group = Group(*remove_list_redundancies( self.group = group_type(*remove_list_redundancies(
[anim.mobject for anim in self.animations] [anim.mobject for anim in self.animations]
)) ))
@ -49,7 +50,7 @@ class AnimationGroup(Animation):
**kwargs **kwargs
) )
def get_all_mobjects(self) -> Group: def get_all_mobjects(self) -> Mobject:
return self.group return self.group
def begin(self) -> None: def begin(self) -> None:

View file

@ -4,26 +4,20 @@ import itertools as it
import numpy as np import numpy as np
from manimlib.animation.animation import Animation
from manimlib.animation.composition import AnimationGroup from manimlib.animation.composition import AnimationGroup
from manimlib.animation.fading import FadeInFromPoint from manimlib.animation.fading import FadeInFromPoint
from manimlib.animation.fading import FadeOutToPoint from manimlib.animation.fading import FadeOutToPoint
from manimlib.animation.fading import FadeTransformPieces from manimlib.animation.fading import FadeTransformPieces
from manimlib.animation.fading import FadeTransform
from manimlib.animation.transform import ReplacementTransform
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Group
from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.mobject.svg.old_tex_mobject import OldTex
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.mobject.svg.old_tex_mobject import SingleStringTex
from manimlib.scene.scene import Scene from manimlib.scene.scene import Scene
@ -151,13 +145,14 @@ class TransformMatchingStrings(AnimationGroup):
match_animation: type = Transform, match_animation: type = Transform,
mismatch_animation: type = Transform, mismatch_animation: type = Transform,
run_time=2, run_time=2,
lag_ratio=0,
**kwargs, **kwargs,
): ):
self.source = source self.source = source
self.target = target self.target = target
matched_keys = matched_keys or list() matched_keys = matched_keys or list()
key_map = key_map or dict() key_map = key_map or dict()
self.anim_config = dict(run_time=run_time, **kwargs) self.anim_config = dict(**kwargs)
# We will progressively build up a list of transforms # We will progressively build up a list of transforms
# from characters in source to those in target. These # from characters in source to those in target. These
@ -208,7 +203,12 @@ class TransformMatchingStrings(AnimationGroup):
target_char, source.get_center(), target_char, source.get_center(),
**self.anim_config **self.anim_config
)) ))
super().__init__(*self.anims) super().__init__(
*self.anims,
run_time=run_time,
lag_ratio=lag_ratio,
group_type=VGroup,
)
def add_transform( def add_transform(
self, self,
@ -230,18 +230,10 @@ class TransformMatchingStrings(AnimationGroup):
self.target_chars.remove(char) self.target_chars.remove(char)
def find_pairs_with_matching_shapes(self, chars1, chars2) -> list[tuple[VMobject, VMobject]]: def find_pairs_with_matching_shapes(self, chars1, chars2) -> list[tuple[VMobject, VMobject]]:
for char in (*chars1, *chars2):
char.save_state()
char.set_height(1)
char.center()
result = [] result = []
for char1, char2 in it.product(chars1, chars2): for char1, char2 in it.product(chars1, chars2):
p1 = char1.get_points() if char1.has_same_shape_as(char2):
p2 = char2.get_points()
if len(p1) == len(p2) and np.isclose(p1, p2 , atol=1e-1).all():
result.append((char1, char2)) result.append((char1, char2))
for char in (*chars1, *chars2):
char.restore()
return result return result
def clean_up_from_scene(self, scene: Scene) -> None: def clean_up_from_scene(self, scene: Scene) -> None:

View file

@ -491,4 +491,6 @@ def get_configuration(args: Namespace) -> dict:
"presenter_mode": args.presenter_mode, "presenter_mode": args.presenter_mode,
"leave_progress_bars": args.leave_progress_bars, "leave_progress_bars": args.leave_progress_bars,
"show_animation_progress": args.show_animation_progress, "show_animation_progress": args.show_animation_progress,
"embed_exception_mode": custom_config["embed_exception_mode"],
"embed_error_sound": custom_config["embed_error_sound"],
} }

View file

@ -42,3 +42,5 @@ camera_resolutions:
4k: "3840x2160" 4k: "3840x2160"
default_resolution: "high" default_resolution: "high"
fps: 30 fps: 30
embed_exception_mode: "Verbose"
embed_error_sound: False

View file

@ -54,22 +54,11 @@ def prompt_user_for_choice(scene_classes):
def get_scene_config(config): def get_scene_config(config):
return dict([ scene_parameters = inspect.signature(Scene).parameters.keys()
(key, config[key]) return {
for key in [ key: config[key]
"window_config", for key in set(scene_parameters).intersection(config.keys())
"camera_config", }
"file_writer_config",
"skip_animations",
"start_at_animation_number",
"end_at_animation_number",
"leave_progress_bars",
"show_animation_progress",
"preview",
"presenter_mode",
]
])
def compute_total_frames(scene_class, scene_config): def compute_total_frames(scene_class, scene_config):
""" """

View file

@ -657,7 +657,7 @@ class Mobject(object):
self.match_updaters(mobject) self.match_updaters(mobject)
return self return self
def looks_identical(self, mobject: Mobject): def looks_identical(self, mobject: Mobject) -> bool:
fam1 = self.family_members_with_points() fam1 = self.family_members_with_points()
fam2 = mobject.family_members_with_points() fam2 = mobject.family_members_with_points()
if len(fam1) != len(fam2): if len(fam1) != len(fam2):
@ -667,15 +667,20 @@ class Mobject(object):
if set(d1).difference(d2): if set(d1).difference(d2):
return False return False
for key in d1: for key in d1:
eq = (d1[key] == d2[key]) if not np.isclose(d1[key], d2[key]).all():
if isinstance(eq, bool): return False
if not eq:
return False
else:
if not eq.all():
return False
return True return True
def has_same_shape_as(self, mobject: Mobject) -> bool:
# Normalize both point sets by centering and making height 1
points1, points2 = (
(m.get_all_points() - m.get_center()) / m.get_height()
for m in (self, mobject)
)
if len(points1) != len(points2):
return False
return bool(np.isclose(points1, points2).all())
# Creating new Mobjects from this one # Creating new Mobjects from this one
def replicate(self, n: int) -> Group: def replicate(self, n: int) -> Group:
@ -1723,7 +1728,7 @@ class Mobject(object):
for sm, sm1, sm2 in zip(self.get_family(), mobject1.get_family(), mobject2.get_family()): for sm, sm1, sm2 in zip(self.get_family(), mobject1.get_family(), mobject2.get_family()):
keys = sm.data.keys() & sm1.data.keys() & sm2.data.keys() keys = sm.data.keys() & sm1.data.keys() & sm2.data.keys()
sm.lock_data(list(filter( sm.lock_data(list(filter(
lambda key: np.all(sm1.data[key] == sm2.data[key]), lambda key: (sm1.data[key] == sm2.data[key]).all(),
keys, keys,
))) )))
return self return self

View file

@ -547,7 +547,7 @@ class StringMobject(SVGMobject, ABC):
def select_parts(self, selector: Selector) -> VGroup: def select_parts(self, selector: Selector) -> VGroup:
specified_substrings = self.get_specified_substrings() specified_substrings = self.get_specified_substrings()
if isinstance(selector, str) and selector not in specified_substrings: if isinstance(selector, (str, re.Pattern)) and selector not in specified_substrings:
return self.select_unisolated_substring(selector) return self.select_unisolated_substring(selector)
indices_list = self.get_submob_indices_lists_by_selector(selector) indices_list = self.get_submob_indices_lists_by_selector(selector)
return self.build_parts_from_indices_lists(indices_list) return self.build_parts_from_indices_lists(indices_list)
@ -563,11 +563,14 @@ class StringMobject(SVGMobject, ABC):
def substr_to_path_count(self, substr: str) -> int: def substr_to_path_count(self, substr: str) -> int:
return len(re.sub(R"\s", "", substr)) return len(re.sub(R"\s", "", substr))
def select_unisolated_substring(self, substr: str) -> VGroup: def select_unisolated_substring(self, pattern: str | re.Pattern) -> VGroup:
if isinstance(pattern, str):
pattern = re.compile(re.escape(pattern))
result = [] result = []
for match in re.finditer(re.escape(substr), self.string): for match in re.finditer(pattern, self.string):
index = match.start() index = match.start()
start = self.substr_to_path_count(self.string[:index]) start = self.substr_to_path_count(self.string[:index])
substr = match.group()
end = start + self.substr_to_path_count(substr) end = start + self.substr_to_path_count(substr)
result.append(self[start:end]) result.append(self[start:end])
return VGroup(*result) return VGroup(*result)

View file

@ -292,15 +292,13 @@ class VMobjectFromSVGPath(VMobject):
def __init__( def __init__(
self, self,
path_obj: se.Path, path_obj: se.Path,
long_lines: bool = False,
should_subdivide_sharp_curves: bool = False, should_subdivide_sharp_curves: bool = False,
should_remove_null_curves: bool = False, should_remove_null_curves: bool = True,
**kwargs **kwargs
): ):
# Get rid of arcs # Get rid of arcs
path_obj.approximate_arcs_with_quads() path_obj.approximate_arcs_with_quads()
self.path_obj = path_obj self.path_obj = path_obj
self.long_lines = long_lines
self.should_subdivide_sharp_curves = should_subdivide_sharp_curves self.should_subdivide_sharp_curves = should_subdivide_sharp_curves
self.should_remove_null_curves = should_remove_null_curves self.should_remove_null_curves = should_remove_null_curves
super().__init__(**kwargs) super().__init__(**kwargs)

View file

@ -148,11 +148,6 @@ class VMobject(Mobject):
raise Exception("All submobjects must be of type VMobject") raise Exception("All submobjects must be of type VMobject")
super().add(*vmobjects) super().add(*vmobjects)
def copy(self, deep: bool = False) -> VMobject:
result = super().copy(deep)
result.shader_wrapper_list = [sw.copy() for sw in self.shader_wrapper_list]
return result
# Colors # Colors
def init_colors(self): def init_colors(self):
self.set_fill( self.set_fill(
@ -809,6 +804,11 @@ class VMobject(Mobject):
# Alignment # Alignment
def align_points(self, vmobject: VMobject): def align_points(self, vmobject: VMobject):
if self.get_num_points() == len(vmobject.get_points()): if self.get_num_points() == len(vmobject.get_points()):
# If both have fill, and they have the same shape, just
# give them the same triangulation so that it's not recalculated
# needlessly throughout an animation
if self.has_fill() and vmobject.has_fill() and self.has_same_shape_as(vmobject):
vmobject.triangulation = self.triangulation
return return
for mob in self, vmobject: for mob in self, vmobject:
@ -1077,14 +1077,6 @@ class VMobject(Mobject):
render_primitive=self.render_primitive, render_primitive=self.render_primitive,
) )
self.shader_wrapper_list = [
self.stroke_shader_wrapper.copy(), # Use for back stroke
self.fill_shader_wrapper.copy(),
self.stroke_shader_wrapper.copy(),
]
for sw in self.shader_wrapper_list:
sw.uniforms = self.uniforms
def refresh_shader_wrapper_id(self): def refresh_shader_wrapper_id(self):
for wrapper in [self.fill_shader_wrapper, self.stroke_shader_wrapper]: for wrapper in [self.fill_shader_wrapper, self.stroke_shader_wrapper]:
wrapper.refresh_id() wrapper.refresh_id()
@ -1107,30 +1099,29 @@ class VMobject(Mobject):
# Build up data lists # Build up data lists
fill_shader_wrappers = [] fill_shader_wrappers = []
stroke_shader_wrappers = [] stroke_shader_wrappers = []
back_stroke_shader_wrappers = []
for submob in self.family_members_with_points(): for submob in self.family_members_with_points():
if submob.has_fill(): if submob.has_fill():
fill_shader_wrappers.append(submob.get_fill_shader_wrapper()) fill_shader_wrappers.append(submob.get_fill_shader_wrapper())
if submob.has_stroke(): if submob.has_stroke():
ssw = submob.get_stroke_shader_wrapper() stroke_shader_wrappers.append(submob.get_stroke_shader_wrapper())
if submob.draw_stroke_behind_fill: if submob.draw_stroke_behind_fill:
back_stroke_shader_wrappers.append(ssw) self.draw_stroke_behind_fill = True
else:
stroke_shader_wrappers.append(ssw)
# Combine data lists self_sws = [self.fill_shader_wrapper, self.stroke_shader_wrapper]
sw_lists = [ sw_lists = [fill_shader_wrappers, stroke_shader_wrappers]
back_stroke_shader_wrappers, for sw, sw_list in zip(self_sws, sw_lists):
fill_shader_wrappers,
stroke_shader_wrappers,
]
for sw, sw_list in zip(self.shader_wrapper_list, sw_lists):
if not sw_list: if not sw_list:
sw.vert_data = resize_array(sw.vert_data, 0)
continue continue
sw.read_in(*sw_list) if sw is sw_list[0]:
sw.combine_with(*sw_list[1:])
else:
sw.read_in(*sw_list)
sw.depth_test = any(sw.depth_test for sw in sw_list) sw.depth_test = any(sw.depth_test for sw in sw_list)
sw.uniforms.update(sw_list[0].uniforms) sw.uniforms.update(sw_list[0].uniforms)
return list(filter(lambda sw: len(sw.vert_data) > 0, self.shader_wrapper_list)) if self.draw_stroke_behind_fill:
self_sws.reverse()
return [sw for sw in self_sws if len(sw.vert_data) > 0]
def get_stroke_shader_data(self) -> np.ndarray: def get_stroke_shader_data(self) -> np.ndarray:
points = self.get_points() points = self.get_points()

View file

@ -16,15 +16,18 @@ import numpy as np
from tqdm import tqdm as ProgressDisplay from tqdm import tqdm as ProgressDisplay
from manimlib.animation.animation import prepare_animation from manimlib.animation.animation import prepare_animation
from manimlib.animation.fading import VFadeInThenOut
from manimlib.camera.camera import Camera from manimlib.camera.camera import Camera
from manimlib.config import get_module from manimlib.config import get_module
from manimlib.constants import ARROW_SYMBOLS from manimlib.constants import ARROW_SYMBOLS
from manimlib.constants import DEFAULT_WAIT_TIME from manimlib.constants import DEFAULT_WAIT_TIME
from manimlib.constants import COMMAND_MODIFIER from manimlib.constants import COMMAND_MODIFIER
from manimlib.constants import SHIFT_MODIFIER from manimlib.constants import SHIFT_MODIFIER
from manimlib.constants import RED
from manimlib.event_handler import EVENT_DISPATCHER from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.event_handler.event_type import EventType from manimlib.event_handler.event_type import EventType
from manimlib.logger import log from manimlib.logger import log
from manimlib.mobject.frame import FullScreenRectangle
from manimlib.mobject.mobject import _AnimationBuilder from manimlib.mobject.mobject import _AnimationBuilder
from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
@ -34,7 +37,6 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene_file_writer import SceneFileWriter from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.family_ops import extract_mobject_family_members from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import recursive_mobject_remove from manimlib.utils.family_ops import recursive_mobject_remove
from manimlib.utils.iterables import list_difference_update
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -74,6 +76,8 @@ class Scene(object):
preview: bool = True, preview: bool = True,
presenter_mode: bool = False, presenter_mode: bool = False,
show_animation_progress: bool = False, show_animation_progress: bool = False,
embed_exception_mode: str = "",
embed_error_sound: bool = False,
): ):
self.skip_animations = skip_animations self.skip_animations = skip_animations
self.always_update_mobjects = always_update_mobjects self.always_update_mobjects = always_update_mobjects
@ -83,6 +87,8 @@ class Scene(object):
self.preview = preview self.preview = preview
self.presenter_mode = presenter_mode self.presenter_mode = presenter_mode
self.show_animation_progress = show_animation_progress self.show_animation_progress = show_animation_progress
self.embed_exception_mode = embed_exception_mode
self.embed_error_sound = embed_error_sound
self.camera_config = {**self.default_camera_config, **camera_config} self.camera_config = {**self.default_camera_config, **camera_config}
self.window_config = {**self.default_window_config, **window_config} self.window_config = {**self.default_window_config, **window_config}
@ -249,6 +255,22 @@ class Scene(object):
shell.events.register("post_run_cell", post_cell_func) shell.events.register("post_run_cell", post_cell_func)
# Flash border, and potentially play sound, on exceptions
def custom_exc(shell, etype, evalue, tb, tb_offset=None):
# still show the error don't just swallow it
shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
if self.embed_error_sound:
os.system("printf '\a'")
self.play(VFadeInThenOut(
FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0),
run_time=0.5,
))
shell.set_custom_exc((Exception,), custom_exc)
# Set desired exception mode
shell.magic(f"xmode {self.embed_exception_mode}")
# Launch shell # Launch shell
shell( shell(
local_ns=local_ns, local_ns=local_ns,

View file

@ -131,7 +131,8 @@ class ShaderWrapper(object):
self.refresh_id() self.refresh_id()
def combine_with(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper: def combine_with(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper:
self.read_in(self.copy(), *shader_wrappers) if len(shader_wrappers) > 0:
self.read_in(self.copy(), *shader_wrappers)
return self return self
def read_in(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper: def read_in(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper:

View file

@ -1,57 +1,41 @@
from __future__ import annotations from __future__ import annotations
import re import re
from functools import lru_cache
from typing import TYPE_CHECKING from manimlib.utils.tex_to_symbol_count import TEX_TO_SYMBOL_COUNT
if TYPE_CHECKING:
from typing import List, Tuple
@lru_cache(maxsize=1)
def get_pattern_symbol_count_pairs() -> List[Tuple[str, int]]:
from manimlib.utils.tex_to_symbol_count import TEX_TO_SYMBOL_COUNT
# Gather all keys of previous map, grouped by common value
count_to_tex_list = dict()
for command, num in TEX_TO_SYMBOL_COUNT.items():
if num not in count_to_tex_list:
count_to_tex_list[num] = []
count_to_tex_list[num].append(command)
# Create a list associating each count with a regular expression
# that will find any tex commands matching that list
pattern_symbol_count_pairs = list()
# Account for patterns like \begin{align} and \phantom{thing}
# which, together with the bracketed content account for zero paths.
# Deliberately put this first in the list
tex_list = ["begin", "end", "phantom"]
pattern_symbol_count_pairs.append(
("|".join(r"\\" + s + r"\{[^\\}]+\}" for s in tex_list), 0)
)
for count, tex_list in count_to_tex_list.items():
pattern = "|".join(r"\\" + s for s in tex_list)
pattern_symbol_count_pairs.append((pattern, count))
# Assume all other expressions of the form \thing are drawn with one path
# Deliberately put this last in the list
pattern_symbol_count_pairs.append((r"\\[a-zA-Z]+", 1))
return pattern_symbol_count_pairs
def num_tex_symbols(tex: str) -> int: def num_tex_symbols(tex: str) -> int:
""" """
This function attempts to estimate the number of symbols that This function attempts to estimate the number of symbols that
a given string of tex would produce. a given string of tex would produce.
Warning, it may not behave perfectly
""" """
# First, remove patterns like \begin{align}, \phantom{thing},
# \begin{array}{cc}, etc.
pattern = "|".join(
r"(\\" + s + ")" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
for s in ["begin", "end", "phantom"]
)
for tup in re.findall(pattern, tex):
tex = tex.replace("".join(tup), " ")
# Progressively count the symbols associated with certain tex commands,
# and remove those commands from the string, adding the number of symbols
# that command creates
total = 0 total = 0
for pattern, count in get_pattern_symbol_count_pairs():
total += count * len(re.findall(pattern, tex)) # Start with the special case \sqrt[number]
tex = re.sub(pattern, " ", tex) # Remove that pattern for substr in re.findall(r"\\sqrt\[[0-9]+\]", tex):
total += len(substr) - 5 # e.g. \sqrt[3] is 3 symbols
tex = tex.replace(substr, " ")
general_command = r"\\[a-zA-Z!,-/:;<>]+"
for substr in re.findall(general_command, tex):
total += TEX_TO_SYMBOL_COUNT.get(substr, 1)
tex = tex.replace(substr, " ")
# Count remaining characters # Count remaining characters
total += sum(map(lambda c: c not in "^{} \n\t_$", tex)) total += sum(map(lambda c: c not in "^{} \n\t_$\\&", tex))
return total return total

View file

@ -1,182 +1,181 @@
TEX_TO_SYMBOL_COUNT = { TEX_TO_SYMBOL_COUNT = {
"!": 0, R"\!": 0,
",": 0, R"\,": 0,
",": 0, R"\-": 0,
"-": 0, R"\/": 0,
"-": 0, R"\:": 0,
"/": 0, R"\;": 0,
":": 0, R"\>": 0,
";": 0, R"\aa": 0,
";": 0, R"\AA": 0,
">": 0, R"\ae": 0,
"aa": 0, R"\AE": 0,
"AA": 0, R"\arccos": 6,
"ae": 0, R"\arcsin": 6,
"AE": 0, R"\arctan": 6,
"arccos": 6, R"\arg": 3,
"arcsin": 6, R"\author": 0,
"arctan": 6, R"\bf": 0,
"arg": 3, R"\bibliography": 0,
"author": 0, R"\bibliographystyle": 0,
"bf": 0, R"\big": 0,
"bibliography": 0, R"\Big": 0,
"bibliographystyle": 0, R"\bigodot": 4,
"big": 0, R"\bigoplus": 5,
"Big": 0, R"\bigskip": 0,
"bigodot": 4, R"\bmod": 3,
"bigoplus": 5, R"\boldmath": 0,
"bigskip": 0, R"\bottomfraction": 2,
"bmod": 3, R"\bowtie": 2,
"boldmath": 0, R"\cal": 0,
"bottomfraction": 2, R"\cdots": 3,
"bowtie": 2, R"\centering": 0,
"cal": 0, R"\cite": 2,
"cdots": 3, R"\cong": 2,
"centering": 0, R"\contentsline": 0,
"cite": 2, R"\cos": 3,
"cong": 2, R"\cosh": 4,
"contentsline": 0, R"\cot": 3,
"cos": 3, R"\coth": 4,
"cosh": 4, R"\csc": 3,
"cot": 3, R"\date": 0,
"coth": 4, R"\dblfloatpagefraction": 2,
"csc": 3, R"\dbltopfraction": 2,
"date": 0, R"\ddots": 3,
"dblfloatpagefraction": 2, R"\deg": 3,
"dbltopfraction": 2, R"\det": 3,
"ddots": 3, R"\dim": 3,
"deg": 3, R"\displaystyle": 0,
"det": 3, R"\div": 2,
"dim": 3, R"\doteq": 2,
"displaystyle": 0, R"\dotfill": 0,
"div": 2, R"\emph": 0,
"doteq": 2, R"\exp": 3,
"dotfill": 0, R"\fbox": 4,
"emph": 0, R"\floatpagefraction": 2,
"exp": 3, R"\flushbottom": 0,
"fbox": 4, R"\footnotesize": 0,
"floatpagefraction": 2, R"\footnotetext": 0,
"flushbottom": 0, R"\frame": 2,
"footnotesize": 0, R"\framebox": 4,
"footnotetext": 0, R"\fussy": 0,
"frame": 2, R"\gcd": 3,
"framebox": 4, R"\ghost": 0,
"fussy": 0, R"\glossary": 0,
"gcd": 3, R"\hfill": 0,
"ghost": 0, R"\hom": 3,
"glossary": 0, R"\hookleftarrow": 2,
"hfill": 0, R"\hookrightarrow": 2,
"hom": 3, R"\hrulefill": 0,
"hookleftarrow": 2, R"\huge": 0,
"hookrightarrow": 2, R"\Huge": 0,
"hrulefill": 0, R"\hyphenation": 0,
"huge": 0, R"\iff": 2,
"Huge": 0, R"\Im": 2,
"hyphenation": 0, R"\index": 0,
"iff": 2, R"\inf": 3,
"Im": 2, R"\it": 0,
"index": 0, R"\ker": 3,
"inf": 3, R"\l": 0,
"it": 0, R"\L": 0,
"ker": 3, R"\label": 0,
"l": 0, R"\large": 0,
"L": 0, R"\Large": 0,
"label": 0, R"\LARGE": 0,
"large": 0, R"\ldots": 3,
"Large": 0, R"\lefteqn": 0,
"LARGE": 0, R"\left": 0,
"ldots": 3, R"\lg": 2,
"lefteqn": 0, R"\lim": 3,
"lg": 2, R"\liminf": 6,
"lim": 3, R"\limsup": 6,
"liminf": 6, R"\linebreak": 0,
"limsup": 6, R"\ln": 2,
"linebreak": 0, R"\log": 3,
"ln": 2, R"\longleftarrow": 2,
"log": 3, R"\Longleftarrow": 2,
"longleftarrow": 2, R"\longleftrightarrow": 2,
"Longleftarrow": 2, R"\Longleftrightarrow": 2,
"longleftrightarrow": 2, R"\longmapsto": 3,
"Longleftrightarrow": 2, R"\longrightarrow": 2,
"longmapsto": 3, R"\Longrightarrow": 2,
"longrightarrow": 2, R"\makebox": 0,
"Longrightarrow": 2, R"\mapsto": 2,
"makebox": 0, R"\markright": 0,
"mapsto": 2, R"\max": 3,
"markright": 0, R"\mbox": 0,
"max": 3, R"\medskip": 0,
"mbox": 0, R"\min": 3,
"medskip": 0, R"\mit": 0,
"min": 3, R"\models": 2,
"mit": 0, R"\ne": 2,
"models": 2, R"\neq": 2,
"ne": 2, R"\newline": 0,
"neq": 2, R"\noindent": 0,
"newline": 0, R"\nolinebreak": 0,
"noindent": 0, R"\nonumber": 0,
"nolinebreak": 0, R"\nopagebreak": 0,
"nonumber": 0, R"\normalmarginpar": 0,
"nopagebreak": 0, R"\normalsize": 0,
"normalmarginpar": 0, R"\notin": 2,
"normalsize": 0, R"\o": 0,
"notin": 2, R"\O": 0,
"o": 0, R"\obeycr": 0,
"O": 0, R"\oe": 0,
"obeycr": 0, R"\OE": 0,
"oe": 0, R"\overbrace": 4,
"OE": 0, R"\pagebreak": 0,
"overbrace": 4, R"\pagenumbering": 0,
"pagebreak": 0, R"\pageref": 2,
"pagenumbering": 0, R"\pmod": 5,
"pageref": 2, R"\Pr": 2,
"pmod": 5, R"\protect": 0,
"Pr": 2, R"\qquad": 0,
"protect": 0, R"\quad": 0,
"qquad": 0, R"\raggedbottom": 0,
"quad": 0, R"\raggedleft": 0,
"raggedbottom": 0, R"\raggedright": 0,
"raggedleft": 0, R"\Re": 2,
"raggedright": 0, R"\ref": 2,
"Re": 2, R"\restorecr": 0,
"ref": 2, R"\reversemarginpar": 0,
"restorecr": 0, R"\right": 0,
"reversemarginpar": 0, R"\rm": 0,
"rm": 0, R"\sc": 0,
"sc": 0, R"\scriptscriptstyle": 0,
"scriptscriptstyle": 0, R"\scriptsize": 0,
"scriptsize": 0, R"\scriptstyle": 0,
"scriptstyle": 0, R"\sec": 3,
"sec": 3, R"\sf": 0,
"sf": 0, R"\shortstack": 0,
"shortstack": 0, R"\sin": 3,
"sin": 3, R"\sinh": 4,
"sinh": 4, R"\sl": 0,
"sl": 0, R"\sloppy": 0,
"sloppy": 0, R"\small": 0,
"small": 0, R"\Small": 0,
"Small": 0, R"\smallskip": 0,
"smallskip": 0, R"\sqrt": 2,
"sqrt": 2, R"\ss": 0,
"ss": 0, R"\sup": 3,
"sup": 3, R"\tan": 3,
"tan": 3, R"\tanh": 4,
"tanh": 4, R"\textbf": 0,
"textbf": 0, R"\textfraction": 2,
"textfraction": 2, R"\textstyle": 0,
"textstyle": 0, R"\thicklines": 0,
"thicklines": 0, R"\thinlines": 0,
"thinlines": 0, R"\thinspace": 0,
"thinspace": 0, R"\tiny": 0,
"tiny": 0, R"\title": 0,
"title": 0, R"\today": 15,
"today": 15, R"\topfraction": 2,
"topfraction": 2, R"\tt": 0,
"tt": 0, R"\typeout": 0,
"typeout": 0, R"\unboldmath": 0,
"unboldmath": 0, R"\underbrace": 6,
"underbrace": 6, R"\underline": 0,
"underline": 0, R"\value": 0,
"value": 0, R"\vdots": 3,
"vdots": 3, R"\vline": 0
"vline": 0
} }