Moebius Strip in Manim - python

I'm trying to make a colored Mobius strip, but I always end up with a color difference between the beginning and the end of the strip.
Here is a MWE and the image it produces.
from manim import *
BG_COLOR = "#F7F5E7"
config.background_color = BG_COLOR
def mobius_func(u, v):
x = (1 + v / 2 * np.cos(u / 2)) * np.cos(u)
y = (1 + v / 2 * np.cos(u / 2)) * np.sin(u)
z = v / 2 * np.sin(u / 2)
return np.array((x, y, z))
class Logo(ThreeDScene):
def construct(self):
self.set_camera_orientation(
phi=50 * DEGREES, theta=330 * DEGREES, run_time=2, zoom=2.2
)
mobius = Surface(
mobius_func,
u_range=[0, 2 * PI],
v_range=[-1, 1],
resolution=(64, 16),
)
mobius.set_color(RED)
self.add(mobius)
What causes this problem and how can I fix it ?

To render a Möbius strip correctly, you need a rendering engine that supports the hidden surface removal(such as z buffering) and face culling. But Manim does not supports them. So in short, you can't solve that problem.
Your best bet will be to hack Manim like this.(Of course, it's not recommended for a long term project, because it maybe will not work on versions other than 0.17.2.)
import numpy as np
import manim
def new_get_shaded_rgb(
rgb: np.ndarray,
point: np.ndarray,
unit_normal_vect: np.ndarray,
light_source: np.ndarray,
) -> np.ndarray:
to_sun = manim.utils.space_ops.normalize(light_source - point)
factor = 0.5 * np.dot(unit_normal_vect, to_sun) ** 3
factor = abs(factor) # This patch will give the same color for a back or front face.
result = rgb + factor
return result
manim.camera.three_d_camera.get_shaded_rgb = new_get_shaded_rgb

Related

Change the terminal-character size (with python)

I am trying to draw shapes in the python terminal but because letters are taller than wide, everything is distorted/stretched. Is there way to change the (don't know how it's called) cell size to something square like 10x10px? I wouldn't care if the actual characters are distorted (for example "H" being squashed together). I work in both Linux and Windows. I have added an example of what I mean. The coordinates of the square are generated with:
def getCPoints(x,y,r,steps):
coords = []
for n in range(steps):
coords.append(((x + r * math.cos(2 * math.pi * (n / steps))),y + r * math.sin(2 * math.pi * (n / steps))))
return coords

quaternion rotation vs. rotation matrix have a slight difference

I have a 3D rotation over time represented as a momentary rotation around each of the axis (roll, pitch, yaw).
I'm trying to accumulate this rotation over time (about 50k measurements in total). I've tried doing it in 2 different ways. Using rotation matrices, and using quaternions calculation. The rotation matrices implementation seem to give correct results, but I know it is less recommended for accumulating many rotations.
The 2 results seem quite similar, but it accumulates a slight difference between the 2 results over time (about 1 degree every 250 measurements). I'm not sure where this difference comes from. Whether it is caused by floating point precision in calculating many matrices multiplications, or by using wrong parameters for the quaternion initialization.
This is the code I use:
# Last known rotation. As quaternion and as rotation matrix
last_rotation_matrix = ....
last_quaternion_rotation = .....
# time_diff_seconds is approximately 4/1000
# the momentary rotation speed is around 0-1 radian per second. so [roll,pitch,yaw] are around 0-0.004)
roll = rotation_x_speed * time_diff_seconds
pitch = rotation_y_speed * time_diff_seconds
yaw = rotation_z_speed * time_diff_seconds
total_rotation = np.sqrt(roll**2 + pitch**2 + yaw**2)
# This function creates a rotation matrix based on the given parameters
diff_rotation_matrix = rotation_matrix(roll, pitch, yaw)
# THIS IS THE LINE THAT I SUSPECT:
diff_quaternion_rotation = Quaternion(axis=[rotation_x_speed, rotation_y_speed, rotation_z_speed], radians=total_rotation)
new_rotation_matrix = diff_quaternion_rotation.dot(last_rotation_matrix)
new_quaternion_rotation = diff_quaternion_rotation * last_rotation_matrix
The line that I suspect is the line initializing the diff_quaternion_rotation variable.
total_rotation = np.sqrt(roll**2 + pitch**2 + yaw**2)
This is wrong - Euler angles cannot be added in this way. Neither is your axis calculation correct.
Instead there is an explicit algorithm for converting Euler angles to quaternions:
(If your custom library doesn't have this function):
cy, sy = cos(yaw * 0.5), sin(yaw * 0.5)
cr, sr = cos(roll * 0.5), sin(roll * 0.5)
cp, sp = cos(pitch * 0.5), sin(pitch * 0.5)
diff_quaternion_rotation = Quaternion(w = cy * cr * cp + sy * sr * sp,
x = cy * sr * cp - sy * cr * sp,
y = cy * cr * sp + sy * sr * cp,
z = sy * cr * cp - cy * sr * sp)

How to change the base of NumPy logistic function?

For an Elo rating system simulation, I would need to draw samples from a logistic distribution in base sqrt(10).
On the NumPy documentation, I have found :
https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.random.logistic.html
This use the probabity density in base "e":
exp((loc-x)/scale)/(scale*(1+exp((loc-x)/scale))**2)
I would need to use the probability density in base sqrt(10).
Do you have any idea how I could do that ?
After checking a bit I'm fairly confident this is correct mathematically:
def logistic_sample(n, loc = 0, scale = 1, base = np.exp(1)):
p = np.random.rand(n)
return loc + scale * np.log(p / (1 - p)) / np.log(base)
Other possibility is to create a new scipy.stats.rv_continuous class if you really need to dig into that distribution:
class logistic_base_gen(scipy.stats.rv_continuous):
def _pdf(self, x, loc, scale, base):
return base ** ((loc - x) / scale) / (s * (1 + base ** ((loc - x) / scale)) ** 2)
logistic_base = logistic_base_gen(0)
logistic_base.rvs(loc, scale, np.sqrt(10), size = n)

How can I calculate tangent with degrees instead of radians?

I am trying to make a basic tool to make my everyday easier, solving some assignments for me. Unfortunately, I can't figure out how to make it calculate in degrees when tangent is being used.
My code:
import math
class Astro():
def start(self):
velocity = input("What is the galaxy's velocity? (m/s) \n")
peculiar = (float(velocity) - 938600) ** 2
mass = (3 * float(peculiar) * (10 ** 11) * 50 * (10 ** 6) * (8 * (180 / math.pi))
* 9.46 * (10 ** 15)) / (2 * 6.67 * (10 ** -11))
print("The galaxy's mass is " + str(mass) + " kg. \n")
if __name__ == '__main__':
sup = Astro()
sup.start()
EDIT: Sorry for the lack of context; this is about calculating the masses of galaxies using 2 functions, the first one, line 7 to get the peculiar velocity, and the second one in lines 8-9 to get the actual mass of the considered galaxy.
SOLVED: math.tan(8 * pi / 180)
Thank you for all your help!
Computers work in radians. Try
answer = tan(angle * pi / 180)
to use your angle in degrees into a trig function. Or try
answer = atan(number) * 180 / pi
to get answer in degrees.
The math package has the functions radians and degrees but under the hood these are just:
def radians(deg):
return deg * pi / 180
def degrees(rad):
return rad * 180 / pi
Here is a wrapper you can use to make degree-using trig functions (just had it lying around somewhere, although I use numpy instead of math)
import math
import itertools
import functools
def _use_deg(f, arc = False):
if not arc:
def df(*args):
args = list(args)
for index, value in enumerate(args):
try:
args[index] = math.radians(value)
except TypeError:
pass
return f(*args)
else:
def df(*args):
return math.degrees(f(*args))
return functools.wraps(f)(df)
sind = _use_deg(math.sin)
cosd = _use_deg(math.cos)
tand = _use_deg(math.tan)
arcsind = _use_deg(math.asin, True)
arccosd = _use_deg(math.acos, True)
arctand = _use_deg(math.atan, True)
arctan2d = _use_deg(math.atan2, True)
You don't want to get in a fight with the math library. Let the math library give you an answer in radians, then multiply it's answer by 180/math.pi to get degrees.

How to make a square inscribed in a circle?

I have to write a function in which there is a square inscribed in a circle. The corners of the square touch the circle's perimeter.
The function call for find_area(4) should have a return value of 18.2400.
But I think the fact that I'm trying to incorporate a square root is messing with the code and not giving me any values.
Here is what I got so far:
import math
def find_area(r):
# area: area of circle - area of square
s = math.sqrt(2)
sidesquare = ( s * ((r*2) / 2)
square = ( sidesquare * 2)** 2
circle = (3.14 * r)** 2
area = circle - square
return (area)
if __name__ == '__main__':
print('Testing compute() with r = 4:' + str(find_area(4)))
There are few problems in your code, mainly you are using few unrequired parenthesis. Hope the below code should help you.
def find_area(r):
s = math.sqrt(2)
sidesquare = s * r * 2 / 2
square = (sidesquare * 2) ** 2
circle = 3.14 * r ** 2
area = circle - square
return area
If you crunch the math through by hand a bit more, you will find that the area of the square is 2 * r ** 2 and the circle is pi * r ** 2, so your function reduces to
from math import pi
def find_area(r):
return (pi - 2.) * r ** 2
or, if you insist on pi == 3.14,
find_area = lambda r: 1.14 * r ** 2

Categories

Resources