Loop to change spring lengths doesn't change anything when I run it - vpython

The project I'm working on is supposed to model a pulse moving down a set of spheres connected by springs. I am trying to decrease the spring length as the pulse moves down the chain, but when I run it, nothing happens.
Here's my code:
from visual import *
one = sphere(pos=(-10,0,0), radius = 0.5, color = color.red)
two = sphere(pos=(-8,0,0), radius = 0.5, color = color.orange)
three = sphere(pos=(-6,0,0), radius = 0.5, color = color.yellow)
four = sphere(pos=(-4,0,0), radius = 0.5, color = color.green)
five = sphere(pos=(-2,0,0), radius = 0.5, color = color.blue)
six = sphere(pos=(0,0,0), radius = 0.5, color = color.cyan)
seven = sphere(pos=(2,0,0), radius = 0.5, color = color.magenta)
eight = sphere(pos=(4,0,0), radius = 0.5, color = color.white)
nine = sphere(pos=(6,0,0), radius = 0.5, color = color.red)
ten = sphere(pos=(8,0,0), radius = 0.5, color = color.orange)
spring1 = helix(pos = (-10, 0, 0), length = 2, radius = 0.3,
thickness = 0.05, color = color.red)
spring2 = helix(pos = (-8, 0, 0), length = 2, radius = 0.3,
thickness = 0.05, color = color.orange)
spring3 = helix(pos = (-6, 0, 0), length = 2, radius = 0.3,
thickness = 0.05, color = color.yellow)
spring4 = helix(pos = (-4, 0, 0), length = 2.0, radius = 0.3,
thickness = 0.05, color = color.green)
spring5 = helix(pos = (-2, 0, 0), length = 2.0, radius = 0.3,
thickness = 0.05, color = color.blue)
spring6 = helix(pos = (0, 0, 0), length = 2.0, radius = 0.3,
thickness = 0.05, color = color.cyan)
spring7 = helix(pos = (2, 0, 0), length = 2.0, radius = 0.3,
thickness = 0.05, color = color.magenta)
spring8 = helix(pos = (4, 0, 0), length = 2.0, radius = 0.3,
thickness = 0.05, color = color.white)
spring9 = helix(pos = (6, 0, 0), length = 2.0, radius = 0.3,
thickness = 0.05, color = color.red)
masses = [one, two, three, four, five, six, seven, eight, nine, ten]
springs = [spring1, spring2, spring3, spring4, spring5, spring6, spring7,
spring8, spring9]
while True:
n=0
deltax=.2
while n < 10:
rate(30)
masses[n].pos.x = masses[n].pos.x + deltax
if n < 9:
springs[n].pos = masses[n].pos
springs[n].axis = masses[n+1].pos-masses[n].pos
n=n+1
n = n-1
while n >= 0:
rate(30)
masses[n].pos.x = masses[n].pos.x - deltax
if n < 0:
springs[n-1].pos = masses[n-1].pos - deltax
springs[n-1].axis = masses[n].pos-masses[n-1].pos
n = n-1
while True:
m=0
deltat=.2
while m<9:
rate(30)
springs[m].length = springs[m].length - deltat
springs[m].length = springs[m].length + deltat
m=m+1
m=m-1
while n>=0:
rate(30)
springs[m].length = springs[m].length - deltat
springs[m].length = springs[m].length + deltat
m=m-1

Make the masses have opacity = 0.3 and replace the first rate statement with scene.waitfor('click'). You'll see that when you move the nth mass to the right, the spring does in fact shorten, and when the (n+1)th mass is moved, the spring to its right shortens, leaving a gap between the nth spring and the (n+1)th spring.
Incidentally, I don't understand where there is a second "while True". It will never be reached.
I'll advertise that a better place to pose VPython questions is in the VPython forum at
https://groups.google.com/forum/?fromgroups&hl=en#!forum/vpython-users

Related

How to pre-position sankey nodes in plotly?

I started using plotly to draw sankey charts.
I want to display a multi year series with birth and death year by year.
The current code looks like:
`
import plotly.graph_objects as go
fig = go.Figure(data=[go.Sankey(
node = dict(
pad = 15,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = ["v11","v21","v31","v41","out21","out31","out41","in21","in31","in41", "v12", "v22"],
color = "blue"
),
link = dict(
source = [0, 7, 0, 1, 8, 1, 2, 9, 2, 10], #
target = [1, 1, 4, 2, 2, 5, 3, 3, 6,11],
value = [1000, 100, 100, 1000, 150, 50, 1000, 120, 80, 800]
))])
fig.update_layout(title_text="Basic Sankey Diagram", font_size=10)
fig.show()
`
which produces something like:
Sankey
All birth nodes are attached to the left side and the out nodes to the right side.
It should however look like this:
Sankey manually rearranged
Is there a way to pin the x-axis?
Or any d3 example, which does the trick?
Thanks for any hint,
Carl
PS: With the following question I got one step further [https://stackoverflow.com/questions/61152889/plotly-how-to-set-node-positions-in-a-sankey-diagram].
The look like this now:
import plotly.graph_objects as go
unique_list = ["c0_v_2021","c0_v_2022","c0_v_2023","c0_v_2024","c0_o_2022","c0_o_2023","c0_o_2024","c0_i_2022","c0_i_2023","c0_i_2024"]
title_list = ["Vol 2021","Vol 2022","Vol 2023","Vol 2024","Out 2022","Out 2023","Out 2024","In 2022","In 2023","In 2024"]
sources = [0, 7, 0, 1, 8, 1, 2, 9, 2]
targets = [1, 1, 4, 2, 2, 5, 3, 3, 6]
values = [1000, 100, 100, 1000, 150, 50, 1000, 120, 80]
## correction based on type
def my_corr(node_corr, steps):
x_corr = []
y_corr = []
for ftype in node_corr:
xcorr = 0
ycorr = 0
if ftype == 'i':
xcorr = - steps/3
ycorr = -0.2
x_corr.append(xcorr)
y_corr.append(ycorr)
return x_corr , y_corr
def my_nodify (node_names):
# node_names = unique_list.copy()
# unique name endings
## get year
ends = sorted(list(set([e[-4:] for e in node_names])))
## get type
corr = (list(([e[-6] for e in node_names])))
min, max = ends[0], ends[-1]
#intervals
steps = 1/((int(max)-int(min)))
x_corr, y_corr = my_corr(corr, steps)
# x-values for each unique name ending for input as node position
nodes_x = {}
xVal = 0
for e in ends:
nodes_x[str(e)] = xVal
xVal += steps
#x and y values in list form
x_values = [nodes_x[n[-4:]] for n in node_names]
## apply x adjustments
x_values_c = [x_values[i] + x_corr[i] for i in range(len(x_corr))]
y_values = []
y_val = 0
for n in node_names:
y_values.append(y_val)
y_val+=.001
y_values.append(y_val)
## apply y adjustments
y_values_c = [y_values[i] + y_corr[i] for i in range(len(y_corr))]
# y_values_c = y_values
return x_values_c, y_values_c
nodified = my_nodify(unique_list)
# plotly setup
fig = go.Figure(data=[go.Sankey(
arrangement='snap',
# arrangement='perpendicular',
node = dict(
pad = 5,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = title_list,
color = "blue",
x=nodified[0], y=nodified[1]
),
link = dict(
source = sources,
target = targets,
value = values
))])
fig.update_layout(
hovermode = 'x',
title="Some Flow",
font=dict(size = 10, color = 'white'),
plot_bgcolor='black',
paper_bgcolor='black'
)
fig.show()
and produces almost what I want although the In-nodes overlapp and the sorting of link is inconsistent.
How can I influence this behaviour?

Why is there distortion when the cube is on the edge of the screen?

I have made a cube renderer from scratch using pygame.I have been following a guide online and translating it into python and have been pretty successful until I started playing around with the program. Everything works fine, however I noticed that when the cube is further from the center, there is a big distortion effect. Like, unreasonably big distortion. Is there something wrong with how the rays are calculated?
(Arrow keys to move camera)
import pygame
width = 720
height = 480
red = (255, 0, 0)
white = (255, 255, 255)
black = (0,0,0)
screen = pygame.display.set_mode((width, height))
running = True
clock = pygame.time.Clock()
runSpeed = 30
cube = [0, 0, 0], [8, 0, 0], [8, 0, 8], [0, 0, 8], [0, 8, 0], [8, 8, 0], [8, 8, 8], [0, 8, 8]
cubeCoords = [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]
moveCubeCoords = [8, -4, -4]
camCoord = [0, 0, 0]
camSpeed = 1
canvasDis = 1
scale = 80
def coordConvert(x, y):
newX = (width / 2) + x
newY = (height / 2) + y
return newX, newY
def drawCircle(x, y):
pygame.draw.circle(screen, red, (x, y), 5)
def moveCube():
for i in range(len(cube)):
for e in range(3):
cube[i][e] = cube[i][e] + moveCubeCoords[e]
def onEvent(event):
if event.key == pygame.K_UP:
camCoord[1] = camCoord[1] + camSpeed
if event.key == pygame.K_DOWN:
camCoord[1] = camCoord[1] - camSpeed
if event.key == pygame.K_RIGHT:
camCoord[2] = camCoord[2] + camSpeed
if event.key == pygame.K_LEFT:
camCoord[2] = camCoord[2] - camSpeed
print(camCoord)
def main():
pygame.init()
print("Initialized")
pygame.display.set_caption("Cube rendering")
moveCube()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
onEvent(event)
for i, point in enumerate(cube): # gets all coords of cube
pX = point[0] - camCoord[0]
pY = point[1] - camCoord[1]
pZ = point[2] - camCoord[2]
mpX = pZ / pX
mpY = pY / -pX # canvas Y deviation from origin
newCoords = coordConvert(mpX * scale, mpY * scale)
cubeCoords[i][0] = newCoords[0]
cubeCoords[i][1] = newCoords[1]
drawCircle(newCoords[0], newCoords[1])
for i in range(4): # Vert lines
pygame.draw.line(screen, black, (cubeCoords[i][0], cubeCoords[i][1]),
(cubeCoords[i + 4][0], cubeCoords[i + 4][1]), 2)
for i in range(3):# Bottom lines
pygame.draw.line(screen, black, (cubeCoords[i][0], cubeCoords[i][1]),
(cubeCoords[i + 1][0], cubeCoords[i + 1][1]), 2)
pygame.draw.line(screen, black, (cubeCoords[3][0], cubeCoords[3][1]),
(cubeCoords[0][0], cubeCoords[0][1]), 2)
for i in range(3): # Top lines
pygame.draw.line(screen, black, (cubeCoords[i+4][0], cubeCoords[i+4][1]),
(cubeCoords[i + 5][0], cubeCoords[i + 5][1]), 2)
#lazy lines
pygame.draw.line(screen, black, (cubeCoords[3][0], cubeCoords[3][1]),
(cubeCoords[0][0], cubeCoords[0][1]), 2)
pygame.draw.line(screen, black, (cubeCoords[7][0], cubeCoords[7][1]),
(cubeCoords[4][0], cubeCoords[4][1]), 2)
print(cubeCoords)
clock.tick(runSpeed)
pygame.display.update()
screen.fill(white)
if __name__ == '__main__':
main()
The only location that a cubemap IS properly projected is the center of the cube. The images have distortion baked into them and seeing them from any other angle with begin to distort.
If you want to move your player around an area you can make the cube REALLY large to minimize the distortion as the player won't move far from the center relative to the cube. This is typical in game environments.
Here's an explanation of cube map projection.
http://wiki.polycount.com/wiki/Cube_map
What can see is called Perspective distortion.
Anyway you can simplify and cleanup the code:
import pygame
pygame.init()
window = pygame.display.set_mode((600, 400))
width, height = window.get_size()
clock = pygame.time.Clock()
cube = [-1, -1, -1], [1, -1, -1], [1, -1, 1], [-1, -1, 1], [-1, 1, -1], [1, 1, -1], [1, 1, 1], [-1, 1, 1]
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)]
cam_coord, cam_speed = [0, 0, 0], 0.5
scale = 200
translate = [4, 0, 0]
for i in range(len(cube)):
for e in range(3):
cube[i][e] += translate[e]
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
cam_coord[1] -= (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * cam_speed
cam_coord[2] -= (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * cam_speed
cube_coords = []
for point in cube:
x = (width / 2) + scale * (point[2] - cam_coord[2]) / (point[0] - cam_coord[0])
y = (height / 2) + scale * (point[1] - cam_coord[1]) / (point[0] - cam_coord[0])
cube_coords.append((x, y))
window.fill((255, 255, 255))
for edge in edges:
pygame.draw.line(window, (0, 0, 0), cube_coords[edge[0]], cube_coords[edge[1]], 2)
for coord in cube_coords:
pygame.draw.circle(window, (255, 0, 0), coord, 5)
pygame.display.update()
pygame.quit()
exit()

How alpha blending works with a transparent background in photoshop

I have two squares: red with color (255, 0, 0) 50% opacity, blue with color (0, 0, 255) 50% opacity
and black opaque background.
At the intersection of these colors, Photoshop shows the color (128, 0, 64) (
photoshp screenshot
).
And I agree whith that. Blue color first blends with black background:
(0, 0, 255) * 0.5 + (0, 0, 0) * ( 1 - 0.5) = (0, 0, 127.5)
alpha = 0.5 + 1 * (1 - 0.5) = 1
Then the result is mixed with red:
(255, 0, 0) * 0.5 + (0, 0, 127.5) * (1 - 0.5) = (127.5, 0, 63.75)
alpha = 0.5 + 1 * (1 - 0.5) = 1
But if background is transparent photoshop gives color (170, 0, 85) with 75% opacity (
photoshp screenshot
).
How does it get that color? I expected (127.5, 0, 127.5) with 75% opacity, because there is nothing in background to blend with.
Following the math described in this article, alpha blending blue square with 50% opacity onto the black background with 0% opacity results in this:
alpha_bg = 0
alpha_fg = 0.5
alpha_blended = alpha_fg + alpha_bg * (1 - alpha_fg) = 0.5
color_blended = ({0, 0, 255} * alpha_fg + {0, 0, 0} * (alpha_bg * (1 - alpha_fg))) / alpha_blended =
({0, 0, 255} * 0.5 + {0, 0, 0} * 0) / 0.5 = {0, 0, 255}
Then, repeating these calculations for blending the red square with 50% opacity on top of the color we calculated above:
alpha_bg = 0.5
alpha_fg = 0.5
alpha_blended = alpha_fg + alpha_bg * (1 - alpha_fg) = 0.5 + 0.5 * (1 - 0.5) = 0.75
color_blended = ({255, 0, 0} * alpha_fg + {0, 0, 255} * (alpha_bg * (1 - alpha_fg))) / alpha_blended =
({255, 0, 0} * 0.5 + {0, 0, 255} * (0.5 * (1 - 0.5))) / 0.75 = {170, 0, 85}

Why did I get all unit ones in this random walk test using Python3?

I am a python beginer and I was wondering why I got all unit ones in below script. I expected to get some integer numbers from 1-10.
import random
def random_walk(n):
"""Return coordinates after 'n' block random walk"""
x, y = 0, 0
for i in range(n):
(dx, dy) = random.choice([(0, 1), (0, -1), (1, 0), (-1, 0)])
x += dx
y += dy
return (x, y)
for i in range(5):
walk = random_walk(10)
print(walk, "Distance from home = ",
abs(walk[0]) + abs(walk[1]))
Output:
(-1, 0) Distance from home = 1
(-1, 0) Distance from home = 1
(-1, 0) Distance from home = 1
(-1, 0) Distance from home = 1
(0, -1) Distance from home = 1
I think this line:
return (x, y)
should be outside of the loop:
import random
def random_walk(n):
"""Return coordinates after 'n' block random walk"""
x, y = 0, 0
for i in range(n):
(dx, dy) = random.choice([(0, 1), (0, -1), (1, 0), (-1, 0)])
x += dx
y += dy
return (x, y)
for i in range(5):
walk = random_walk(10)
print(walk, "Distance from home = ",
abs(walk[0]) + abs(walk[1]))
Output:
(1, 1) Distance from home = 2
(-1, 1) Distance from home = 2
(-3, 3) Distance from home = 6
(0, -2) Distance from home = 2
(1, 3) Distance from home = 4

Animation is not working on a matplotlib gridspec

I have reviewed the answer to the question:
how to use GridSpec() with FuncAnimation in matplotlib?
however I believe I am doing something wrong but do not know what, my mission here is to create multiple animations for each of the grids and I also used a code mode for a gauge to animate the count of used data.
x1 = np.random.normal(-2.5, 1, 10000)
x2 = np.random.gamma(2, 1.5, 10000)
x3 = np.random.exponential(2, 10000)+7
x4 = np.random.uniform(14,20, 10000)
xrand = np.random.random(size = 10000)
x = [x1, x2, x3, x4]
plt.figure()
gspec = gridspec.GridSpec(3,3, wspace = 0.25)
GS1 = plt.subplot(gspec[0, 0])
GS2 = plt.subplot(gspec[0, 1])
GS3 = plt.subplot(gspec[1, 0])
GS4 = plt.subplot(gspec[1, 1])
GS5 = plt.subplot(gspec[0:,2:])
GS6 = plt.subplot(gspec[2,:2])
GS = [GS1, GS2, GS3, GS4, GS5, GS6]
bins1 = np.arange(-7.5, 2.5, 0.2)
bins2 = np.arange(0, 10, 0.2)
bins3 = np.arange(7, 17, 0.2)
bins4 = np.arange(12, 22, 0.2)
bins = [bins1, bins2, bins3, bins4]
axis1 = [-7.5, 2.5, 0, 0.6]
axis2 = [0, 10, 0, 0.6]
axis3 = [7, 17, 0, 0.6]
axis4 = [12, 22, 0, 0.6]
axis = [axis1, axis2, axis3, axis4]
GS1.hist(x1, bins = 1000)
GS2.hist(x2, bins = 1000)
GS3.hist(x3, bins = 1000)
GS4.hist(x4, bins = 1000)
GS5.scatter(x1, xrand, norm = True, c = 'r', s= 0.7)
GS5.scatter(x2, xrand, norm = True, c = 'g', s= 0.7)
GS5.scatter(x3, xrand, norm = True, c = 'b', s= 0.7)
GS5.scatter(x4, xrand, norm = True, c = 'y', s= 0.7)
for s in GS:
s.spines['right'].set_visible(False)
s.spines['top'].set_visible(False)
gspec.update(wspace = .6, hspace = .6)
fig, ((GS1,GS2),(GS3, GS4)) = plt.subplots(2, 2, sharey = True)
GS = [GS1, GS2, GS3, GS4]
def update(curr):
if curr == 500:
a.event_source.stop()
for i in range(0, len(GS)):
GS[i].hist(x[i][:curr], bins = bins[i], normed = True)
GS[i].axis(axis[i])
GS[i].gca().set_title('Sampling random distribution')
GS[i].gca().set_ylabel('Frequency')
GS[i].gca().set_xlabel('Value')
GS[i].annotate('n = {}'.format(curr), [3,27])
plt.tight_layout()
plt.show()
fig = plt.gcf()
a = animation.FuncAnimation(fig, update, interval = 10) #, blit = True, repeat = True)
I do not understand what is the problem with the code.
Problems:
You cannot use plt.show() inside the updating function.
GS[i].gca().set_title does not make sense, because GS[i] already is an axes, which does not have any gca() method. Same for set_ylabel and set_xlabel.
You need to call tight_layout() only once outside the loop.
You will need to remove the old histogram before plotting a new one on the axes.
Possibly the following is what you're after:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import animation
x1 = np.random.normal(-2.5, 1, 10000)
x2 = np.random.gamma(2, 1.5, 10000)
x3 = np.random.exponential(2, 10000)+7
x4 = np.random.uniform(14,20, 10000)
xrand = np.random.random(size = 10000)
x = [x1, x2, x3, x4]
bins1 = np.arange(-7.5, 2.5, 0.2)
bins2 = np.arange(0, 10, 0.2)
bins3 = np.arange(7, 17, 0.2)
bins4 = np.arange(12, 22, 0.2)
bins = [bins1, bins2, bins3, bins4]
axis1 = [-7.5, 2.5, 0, 0.6]
axis2 = [0, 10, 0, 0.6]
axis3 = [7, 17, 0, 0.6]
axis4 = [12, 22, 0, 0.6]
axis = [axis1, axis2, axis3, axis4]
fig, ((GS1,GS2),(GS3, GS4)) = plt.subplots(2, 2, sharey = True)
GS = [GS1, GS2, GS3, GS4]
def update(curr):
if curr == 500:
a.event_source.stop()
for i in range(0, len(GS)):
GS[i].clear()
GS[i].hist(x[i][:curr], bins = bins[i], normed = True)
GS[i].axis(axis[i])
GS[i].set_title('Sampling random distribution')
GS[i].set_ylabel('Frequency')
GS[i].set_xlabel('Value')
GS[i].annotate('n = {}'.format(curr), [3,27])
plt.tight_layout()
fig = plt.gcf()
a = animation.FuncAnimation(fig, update, interval = 10)
plt.show()

Resources