Intelligence Generator Projects

Neuroscience based machine intelligence models by Gary Gaulin

Wednesday, October 25, 2023

Create Vesicle using Lennard Jones Potential, and propagate Traveling Waves over surface

import sys
import pygame
import random
import numpy as np
import os

program_dir = os.getcwd() # Get the Folder path this Python Programm is running from.
image_dir = program_dir + '\Images' # Directory to PNG Image files
#if not os.path.exists(image_dir): # If no directory folder for PNG files yet
# os.mkdir('Images') # then make new directory.

signalcollapsepoint = 0 # Program start point of Wave, oint where wave collapsed on other side.
signalactivity = 0 # Total number of connections that are spiking/on/set/1 during this time cycle.
defaultpointcount = 1024 # Number of particles when no StartupXYZ.txt file. Symmetrical are 32, 360.
pressureRampStep = .0025
pressureMin = 1
pressureMax = 1
pressureRampDirection = 1
adjustR = 1.01 # Shaking pressure like in jar of water, can help jog into place.
forceCutOff = 2 # Amount of spacing distance at which forces are too small to influence.
groupCutOff = 3.5
connectCutOff = 1.3 # Amount of spacing between neighbors to connect to.
rotationstep = .0125 # Amount of rotation per timestep.
rotation = 0 # Initialize rotation angle, to draw all points on screen.
epsilon = 1 # Lennard Jones variable, remains 1, normalized.
sigma = 1 # Lennard Jones variable, remains 1, normalized.
dt = .025 # Timestep size, how (slower but) precise it moves.
maxf = 2 # Velocity limit for when enough force to fly off sphere.
popwaitcount = 400
popwait = 0
pops = 0

height = 520 # Size in pixels for each of 2 squares to draw inside.
screensize = (height*2, height) # Pygame variable used to initialize screen window.
screenradius = int(height*.43) # Radius of vesicle in pixels, when drawn to screen.
xc1 = int(screensize[0]*.25) # Center x screen location for left square.
xc2 = int(screensize[0]*.75) # Center x screen location for square to right.
yc = int(screensize[1]*.5) # Center y screen location point for both squares.
timestepnumber = 0 # Counter for number of times program was stepped/looped one moment in time.

pi = 3.14159265358979323846264338327950288412
radian = pi * 2
threeQtrRadian = radian * 3/4

Black = (0, 0, 0)
Brown = (55, 0, 0)
Red = (255, 0, 0)
Orange = (255, 165, 0)
Yellow = (220, 220, 0)
Green = (0, 200, 0)
Blue = (0, 0, 150)
Violet = (255, 0, 255)
Gray = (120, 120, 120)
White = (255, 255, 255)
Cyan = (0, 255, 255)

# Colors used in electronics to mark resistors and other components with bands, to make easy to read not tiny numbers.
# These are used to similarly identify number of connections each cell has by color, to make easy to see what they are.
# 0=Black, 1=Brown, 2=Red, 3=Orange, 4=Yellow, 5=Green, 6=Blue, 7=Violet, 8=Gray, 9=White
ElectronicColorCode = Black, Brown, Red, Orange, Yellow, Green, Blue, Violet, Gray, White
# Colors used to draw connections that are either 0, 1 = action potential.
InputSignalColor = Black, Cyan # Colors for connections that just propagated the bit to next point/cell.
OutputSignalColor = Black, Red # Full on for entire timecycle is Red.


def csvwrite(sourcelist, filename): # Write Comma Separated Value list of lists [,,] or tuples (,,) to disk.
# For control over file behavior csv or other import module is not used for reading or writing.
with open(filename, 'w') as f: # Open file to write over, using 'f' as its name:
for line in sourcelist: # For each line of values in source list:
fs = str(line) # Convert line to a string containing commas.
f.write(fs[1:-1] + "\n") # Write all but [] or () chars, line feed at end.


# File to read must have no blank lines or comments added to it, comma separated values only.
def xyzread(filename): # Read disk file back into list of lists.
x = []
y = []
z = []
try:
with open(filename, 'r') as f: # Open file in read mode using 'f' as its name:
for line in f: # For each comma separated line of code in 'f':
fs = line.rstrip('\n') # Remove line x,y,z readings in line into s
if len(fs) > 0:
fs = fs.split(',') # Split line at commas into list named s
x.append(float(fs[0])) # Save first value in s as x reading.
y.append(float(fs[1])) # Save second value in s as y reading.
z.append(float(fs[2])) # Save third value in s as z reading.
except IOError: # XYZ File does not exist. Start new vesicle.
x = [0.0 for _ in range(defaultpointcount)]
y = [0.0 for _ in range(defaultpointcount)]
z = [0.0 for _ in range(defaultpointcount)]
# Randomly distribute points inside sphere by filling a cube but only save points inside.
for i in range(defaultpointcount): # For each x,y,z reading to save in list.
ra = screenradius + 1
while ra > screenradius: # Loop until radius is inside sphere.
x[i] = random.uniform(-screenradius, screenradius)
y[i] = random.uniform(-screenradius, screenradius)
z[i] = random.uniform(-screenradius, screenradius)
az, el, ra = cart2sphere(x[i], y[i], z[i]) # Generate random Cartesian point inside a cube.
return x, y, z


# Write Comma Separated Value list of lists [,,] or tuples (,,) to disk.
def xyzwrite(filename):
# For control over file behavior csv or other import module is not used for reading or writing.
with open(filename, 'w') as f: # Open file to write over, using 'f' as its name:
for i in range(points):
sxyz = str([xp[i], yp[i], zp[i]]) # For each value in the 3 source lists
f.write(sxyz[1:-1] + "\n") # Write all but [] or () chars, line feed at end.
screen.fill(White) # Make screen background flash white to indicate file save.


# File to read must have no blank lines or comments added to it, comma separated values only.
def csvread(filename): # Read disk file back into list of lists.
destination = [] # Clear destination list lists are appended to.
with open(filename, 'r') as f: # Open file in read mode using 'f' as its name:
for line in f: # For each comma separated line of code in 'f':
fs = line.rstrip('\n')
d = [] # Empty list named 'd' to fill with elements.
if len(fs) > 0:
fs = fs.split(',') # Split line at commas into list named 's'
for e in fs: # For each string element listed in 's' list:
d.append(float(e)) # Append floating point element value to list.
destination.append(d) # Append list of elements to destination list.
return destination # Returns list of lists only = [[,,],[,,],[,,]]


# Calculate spherical angle force vectors of a point are together most pointing towards.
# Magnitude becomes 0 when neighboring points are in opposite or opposing each other.
# icenter is the point to calculate.
def vectorsum(icenter, listname):
xsum = 0.0
ysum = 0.0
zsum = 0.0
for i in listname:
xctr = xp[icenter]
yctr = yp[icenter]
zctr = zp[icenter]
xsum += xp[i] - xctr
ysum += yp[i] - yctr
zsum += zp[i] - zctr
az, el, ra = cart2sphere(xsum, ysum, zsum)
return az, el, ra


# Cartesian to Spherical coordinates.
def cart2sphere(x, y, z):
hxy = np.hypot(x, y)
return np.arctan2(y, x), np.arctan2(z, hxy), np.hypot(hxy, z)


# Spherical to Cartesian coordinates.
def sphere2cart(az, el, ra):
theta = ra * np.cos(el)
x = theta * np.cos(az)
y = theta * np.sin(az)
z = ra * np.sin(el)
return x, y, z


# To make program faster: Return a list of nearbby points less than (too far to attract) CutOff distance away.
def update_grouplist():
print(timestepnumber, 'Compiling Group list')
groupsum = 0
for i in range(points): # For all the points in array
grouplist[i] = []
for j in range(points): # For all the other points in array
if i != j: # Except itself
dx = xp[j] - xp[i] # Calculate distance between points along x.
if abs(dx) <= groupR: # If (neg made positive) x distance OK continue.
dy = yp[j] - yp[i] # Calculate distance between points along y.
if abs(dy) <= groupR: # If (neg made positive) y distance OK continue.
dz = zp[j] - zp[i] # Calculate distance between points along z.
if abs(dz) <= groupR: # If (neg=pos.) z OK then time consuming sqrt precision.
dist = np.sqrt((dx * dx) + (dy * dy) + (dz * dz)) # Calculate 3D distance.
if dist < groupR: # If distance is within group range then.
grouplist[i].append(j) # Append i and j points to the end of list.
groupsum += len(grouplist[i])
print("Updated Group list, ", len(grouplist), "total distances, average", groupsum / points, "per point.")


# Return a list of points within cutoff range.
def update_cutofflist():
print(timestepnumber, 'Compiling CutOff list')
cutoffsum = 0
for i in range(len(grouplist)):
cutofflist[i] = []
for j in grouplist[i]:
if i != j: # Except itself
dx = xp[j] - xp[i] # Calculate distance between points along x.
if abs(dx) <= cutoffR: # If (neg made positive) x distance OK continue.
dy = yp[j] - yp[i] # Calculate distance between points along y.
if abs(dy) <= cutoffR: # If (neg made positive) y distance OK continue.
dz = zp[j] - zp[i] # Calculate distance between points along z.
if abs(dz) <= cutoffR: # If (neg=pos.) z OK then time consuming sqrt precision.
dist = np.sqrt((dx * dx) + (dy * dy) + (dz * dz)) # Calculate distance between.
if dist < cutoffR: # If distance is within cutoff range then
cutofflist[i].append(j) # Append i and j points to the end of list.
cutoffsum += len(cutofflist[i])
print("Updated CutOff list, ", len(cutofflist), "total distances, average", cutoffsum/points, "per point.")
return cutoffsum/points


# Return a list of particles within group range, and another list for close enough to form in/out connections to.
def update_connections():
global connections
for i in range(points):
connectlist[i] = []
for j in cutofflist[i]:
if i != j:
dx = xp[j] - xp[i] # Calculate x,y,z distances.
dy = yp[j] - yp[i]
dz = zp[j] - zp[i]
dist = np.sqrt((dx * dx) + (dy * dy) + (dz * dz)) # Calculate 3D distance, a time consuming command.
if dist < connectR: # If close enough to connect together then
connectlist[i].append(j) # save j point at this i, makes list of what connects to where.
connections[i] = len(connectlist[i])


# Update (azimuth) longitude and (elevation) latitude coordinates. Radius for all points is that of sphere, not needed.
def update_angles():
global latlong
latlong = []
for i in range(points):
az, el, ra = cart2sphere(xp[i], yp[i], zp[i]) # Spherical azimuth,elevation = longitude,latitude.
latlong.append([-el, az]) # Elevation is negative, north pole is positive latitude.
# print(latlong)


def update_positions():
global cutofflist
if holdradius:
for i in range(points):
# Hold to sphere. Convert points to spherical coordinates then back using sphere radius as center distance.
xp[i], yp[i], zp[i] = sphere2cart(np.arctan2(yp[i], xp[i]), # Calc azimuth.
np.arctan2(zp[i], np.hypot(xp[i], yp[i])), # Calc elevation.
vesicleR) # Desired sphere radius.
# Initialize velocity vector arrays for summing forces, combined angles and magnitudes.
xi = [0 for _ in range(points)]
yi = [0 for _ in range(points)]
zi = [0 for _ in range(points)]
xj = [0 for _ in range(points)]
yj = [0 for _ in range(points)]
zj = [0 for _ in range(points)]
# Clear sum, minimum and maximum
ljt = 0 # Initialize variable for totaling Lennard-Jones force.
ljmaximum = -10000 # Initialize for finding max, will be made greatest reading.
ljmaximum_i = 0 # For also saving max 'i' number of greatest reading.
ljminimum = 100000000000 # Initialize for finding min, will be made greatest reading.
ljminimum_i = 0 # For also saving min 'i' number of greatest reading.
ljminimum_j = 0 # For also saving min 'j' number of greatest reading.
atmax = 0
for i in range(len(cutofflist)): # For all the points in array.
ljsum[i] = 0
ljt1 = 0
for j in cutofflist[i]:
# Calculate x,y,z distances.
dx = xp[j] - xp[i]
dy = yp[j] - yp[i]
dz = zp[j] - zp[i]
# Use the three distances for calculating an always positive 3D distance.
dist = np.sqrt((dx * dx) + (dy * dy) + (dz * dz))
# Calculate Lennard Jones force.
ljr = dist/spacing # Lennard-Jones radius. At r=1 force is 0, spacing=r*2
lj = 4 * epsilon * ((sigma/ljr)**12 - (sigma/ljr)**6)
# Reduce timestep motion for smooth movement.
f = lj * dt
if f > maxf: # Limit amount of displacement to not fly off screen.
f = maxf
atmax = 1
# f = np.sqrt((x * x) + (y * y) + (z * z))
ljt1 += lj # Add to sum for average of all points.
ljsum[i] += f # Used for brightness to draw on screen.
if lj > ljmaximum: # If force is greater than last greatest then:
ljmaximum = lj # Save maximum reading.
ljmaximum_i = i # Also save max 'i' number of greatest reading.
if lj < ljminimum: # If force is lower than last lowest then:
ljminimum = lj # Save minimum reading.
ljminimum_i = i # Also save min 'i' number of lowest reading.
ljminimum_j = j # Also save min 'j' number of lowest reading.
# if f < -maxf: # Limit amount of displacement to not fly off screen.
# f = -maxf
# Use distance for calculating spherical azimuth and elevation angle r, as in cart2sphere.
x, y, z = sphere2cart(np.arctan2(dy, dx), np.arctan2(dz, dist), f)
# Update x,y,z force acting on i point, from this j point.
xi[i] -= x
yi[i] -= y
zi[i] -= z
# Update other point, j. For every action there is equal and opposite reaction.
xj[j] += x
yj[j] += y
zj[j] += z
# ljt += ljt1 / len(cutofflist[i]) # Add to sum for average of all points.
listLen = len(cutofflist[i])
if listLen > 0: # List length must be greater than zero or division by zero error.
ljt += ljt1 / len(cutofflist[i]) # Add to sum for average of all points.
# Add force displacement amounts, to points.
for i in range(points):
xp[i] += xi[i] + xj[i]
yp[i] += yi[i] + yj[i]
zp[i] += zi[i] + zj[i]
listLen = len(cutofflist[i])
if listLen > 0: # List length must be greater than zero or division by zero error.
ljsum[i] = ljsum[i] / len(cutofflist[i])
return ljt/points, ljminimum, ljminimum_i, ljminimum_j, ljmaximum, ljmaximum_i, atmax


def render():
# Precalculate all the rotated X,Y screen locations and their colors.
for i in range(points):
az, el, r = cart2sphere(xp[i], yp[i], zp[i]) # Cartesian to Spherical (az, el, r)
xr, yr, zr = sphere2cart(az + rotation, el, r) # Spherical to Cartesian.
xyzi[i] = [xr, yr, zr, i]
unsortedxyzi[i] = xyzi[i]
# To properly draw on top of each other sort the list according to what is furthest away, [1] = by Y axis.
xyzi.sort(key=lambda x: x[1])
if drawcolors != 0:
for i in range(points): # Draw all points to screen.
drawvesicle(xc1, 1, i) # Draw left view i point to screen in proper color.
drawvesicle(xc2, -1, points-1-i) # Draw right view i point to screen in proper color.
if drawwaves is True:
for i in range(points):
drawsignallines(xc1, 1, i) # Draw left view signal lines from given i point.
drawsignallines(xc2, -1, points-1-i) # Draw right view signal lines from given i point.
if axeslines: # Draw x,y,z axis lines to screen.
ra = vesiclestartR * 1.1 # Length of axis line to draw.
drawaxisline(0, 0, ra, Yellow)
if rotation > threeQtrRadian: # Green x behind blue y?
drawaxisline(ra, 0, 0, Green)
drawaxisline(0, ra, 0, Blue)
if rotation < threeQtrRadian: # Green x on top of blue y?
drawaxisline(ra, 0, 0, Green)
if pops > 0:
drawpop(xc1, 1, White, xpop, ypop, zpop)
drawpop(xc2, -1, White, xpop, ypop, zpop)
drawpop(xc1, 1, Cyan, xnew, ynew, znew)
drawpop(xc2, -1, Cyan, xnew, ynew, znew)
pygame.display.update() # Display everything drawn into screen buffer.


# Draw one i point filled circle in sorted xyz list. For next loop in render draws all vesicle points.
def drawpop(xc, direction, c, x, y, z):
az, el, r = cart2sphere(x, y, z) # Cartesian to Spherical (az, el, r)
xr, yr, zr = sphere2cart(az + rotation, el, vesicleR) # Spherical to Cartesian.
if yr*direction < 0:
return
xs = int(xr * direction + xc) # Screen pixel x location to draw at.
ys = int(zr + yc) # Screen pixel y location to draw at.
cr = int(spacing/2) + 12 # Circle radius after distance away is factored in.
pygame.draw.circle(screen, c, [xs, ys], cr, 5) # Draw circle to screen.


# Draw one i point filled circle in sorted xyz list. For next loop in render draws all vesicle points.
def drawvesicle(xc, direction, i):
x, y, z, ui = xyzi[i] # Get x,y,z and unsorted 'i' from sorted list.
if y*direction < -spacing:
return
c = Black # Default fill color is black, when drawcolors=0
if drawcolors == 1: # Keyboard 'c' changes, 1=LennardJones colors.
c = ljcolor(ui) # Use Lennard-Jones amount, warm to hot colors.
if drawcolors == 2: # Keyboard 'c' changes, 2=Connections based colors.
c = connectcolor(ui) # Electronic color code, number of connections each.
xs = int((x*direction) + xc) # Screen pixel x location to draw at.
ys = int(z + yc) # Screen pixel y location to draw at.
d = (((y*direction)+vesiclestartR)/spherewidth*.4) + .6 # Fraction according to how far away, depth.
if d > 1: # If fractional depth is greater than 1 then
d = 1 # reduce to 1, close as possible to observer.
cr = int(d * spacing * .5) + 1 # Circle radius after distance away is factored in.
pygame.draw.circle(screen, c, [xs, ys], cr, 0) # Draw filled circle to screen.
if outlines:
pygame.draw.circle(screen, White, [xs, ys], cr, 1) # Draw outline circle around it.
if drawnumbers:
fnt = pygame.font.SysFont('arial', int(halfspacing))
txtsurf = fnt.render(str(ui), False, White)
textwidth = fnt.size(str(ui))[0]
screen.blit(txtsurf, (xs - textwidth/2, ys - quarterspacing))


# Draw signal lines outward from one given i in sorted xyz list. For next loop in render draws all vesicle points.
def drawsignallines(xc, direction, i):
global connectlist
global outs
x, y, z, ui = xyzi[i] # Get variables from sorted list of x,y,z points.
if y*direction < -spacing:
return
x1 = int((x*direction) + xc) # Screen pixel x location to draw at.
y1 = int(z + yc) # Screen pixel y location to draw at.
d = (((y*direction)+vesicleR) / spherewidth) # Fraction according to how far away, depth.
if d > 1: # If fractional depth is greater than 1 then
d = 1 # reduce to 1, close as possible to observer.
outbits = outs[ui]
for j in range(len(connectlist[ui])):
x2, y2, z2, ui2 = unsortedxyzi[connectlist[ui][j]]
b = outbits[j]
if b == 1:
thick = int((b * d * spacing / 3) + 1)
c = OutputSignalColor[b]
if c != Black:
pygame.draw.line(screen, c, [x1, y1], [(x2*direction)+xc, z2+yc], thick)
inbits = ins[ui]
for j in range(len(connectlist[ui])):
x2, y2, z2, ui2 = unsortedxyzi[connectlist[ui][j]]
b = inbits[j]
thick = int((b * d * spacing / 9) + 1)
c = InputSignalColor[b]
if c != Black:
pygame.draw.line(screen, c, [x1, y1], [(x2*direction)+xc, z2+yc], thick)


def save():
os.system("ffmpeg -r 1 -i img%01d.png -vcodec mpeg4 -y movie.mp4")


def drawaxisline(x, y, z, c):
az, el, ra = cart2sphere(x, y, z) # Cartesian to Spherical (az, el, r)
x1, y1, z1 = sphere2cart(az + rotation, el, ra) # Convert from Spherical to Cartesian.
pygame.draw.line(screen, c, (xc1, yc), (x1+xc1, z1+yc), 1)


# Vary brightness according to amount of force. Positive is orange to white, negative (attraction) blue.
def ljcolor(t):
if ljsum[t] >= 0:
poscolor = int(ljsum[t] * (256/maxf) * 10)
if poscolor > 255:
poscolor = int(poscolor/200)
if poscolor > 255:
poscolor = 255
return 255, 240, poscolor
else:
return poscolor, poscolor * .75, 0
if ljsum[t] < 0:
negcolor = int(-ljsum[t] * 2000)
if negcolor > 255:
negcolor = 255
return 0, negcolor*.6, negcolor

def connectcolor(n):
cns = len(connectlist[n])
if cns > 9:
cns = 9
return ElectronicColorCode[cns]


def timestepsignals():
global ins
global outs
global signalactivity
global signalcollapsepoint
signalactivity = 0
ins = [[] for _ in range(points)] # Clear Input list output bits from neighbors are gathered into.

# For all points in array, gather bits from neighbors to a list for each. Example: ins[i,[1,0,0,0,1,0]]
for i in range(points): # For each point in vesicle, each with connections to 'j's
for j in range(len(connectlist[i])): # For each of the (usually 6) 'j' connections to other cells,
if j < len(outs[i]): # If output connection still exists in connection list then
b = outs[i][j] # bit to save is from output list.
else: # Else
b = 0 # to prevent program error append with 0 to fill void later.
ins[connectlist[i][j]].append(b) # Append bit from 'j' output to corresponding input line at 'i'

# If any bits are 1 then propagate wave using NOT to output inverted/opposite bit pattern. 1=0 and 0=1
for i in range(points): # For each 'i' point in vesicle, each with connections to 'j's
outs[i] = []
setbits = sum(ins[i])
signalactivity += setbits # Signal activity equals total number of bits set, entire surface.
if setbits > 0: # If there are any bits set then
outs[i] = [int(not b) for b in ins[i]] # all 1's in output list become 0, and all 0 becomes 1, negates.
else: # Else
outs[i] = ins[i] # Output bits remain all 0.

# If no signal activity then start wave by setting all output connections of a randomly selected place to 1.
if signalactivity == 0: # If no signal activity at all then
# startwave(random.randint(0, points-1)) # Randomly choose the point to start wave from.
startwave(4) # Start wave at same given point each time.

# If uncontrolled signal activity then clear all outputs.
if signalactivity > points * 2: # If more than average two connections per cell then
clearwave()


# Start wave from cell at point number given by value of i
def startwave(i):
global signalactivity
outs[i] = [1] * len(connectlist[i]) # Output 1, action potential, to all neighbors.
signalactivity += 6
print('~~~~~~~~~~ Wave started from point number', i, '~~~~~~~~~~')


# For when extreme hexagonal misalignment causes uncontrollable seixure-like activity.
def clearwave():
for i in range(points): # for all points in network.
outs[i] = [0] * len(outs[i]) # fill list with 0's, same length as there are out connections.
screen.fill(White)
myfont = pygame.font.SysFont('arial', 68)
textsurface = myfont.render("STOP", False, Black)
screen.blit(textsurface, (height * .82, height * .8))
print('~~~~~~~~~~ All connection outputs were set to 0 ~~~~~~~~~~')


# To Squeeze and Release pressure from all around.
def pressureRamp(press, direction):
newpressure = press + (pressureRampStep * direction)
if newpressure > pressureMax:
newpressure = pressureMax
direction = -1
if newpressure < pressureMin:
newpressure = pressureMin
direction = 1
return newpressure, direction


def listlennardjones():
lj = 0
for i in range(32):
d = i / 16
# ljr = d / sp # Lennard-Jones radius. At ljr=1 force is 0.
if d > 0: # Prevent divide by zero error when LJ radius is zero.
lj = 4 * epsilon * ((sigma / d) ** 12 - (sigma / d) ** 6)
print(lj, d)


#######################################################################################################################
xp, yp, zp = xyzread("StartupXYZ.txt") # Load either startup file, or random positions for new vesicle.
points = len(xp) # Number of points total in vesicle.
ljavr = 0.0
ljmax = 0.0
ljsum = [0 for _ in range(points)] # Initialize Lennard-Jones sum array.
xyzi = [[] for _ in range(points)] # List to sort, to draw according to their distance from observer.
unsortedxyzi = [[] for _ in range(points)] # Above list before sorting.
grouplist = [[] for _ in range(points)] # Nearby points in larger group, stay with for very long time.
cutofflist = [[] for _ in range(points)] # Points within cutoff range.
connectlist = [[] for _ in range(points)] # Connections from each i point, to neighbors j.
connections = [[] for _ in range(points)] # Connections from each i point, to neighbors j.
latlong = [[] for _ in range(points)] # Spherical coordinates for each point.
cutoffconnects = [[] for _ in range(points)] # Number of connections, usually 6.
ins = [[] for _ in range(points)] # Input (action potential) bits, from each 1 bit connection.
outs = [[] for _ in range(points)] # Output (action potentials) bits, to each 1 bit connection.

# PyGame Initialization
pygame.init()
screen = pygame.display.set_mode(screensize)
pygame.display.set_caption("Lennard Jones Potential Vescicle. A=Axis, C=ColorCode, F=Forces, H=HoldToSphere, N=indexNumber, P=Pressure, W=Waves, Spacebar=Pause, S=Save-XYZ.txt")
fps = pygame.time.Clock()
pygame.font.init()
# Display 'Initializing nnnnn points' message.
myfont = pygame.font.SysFont('arial', 50)
s = 'Initializing ' + str(points) + ' points'
textsurface = myfont.render(s, False, White)
screen.blit(textsurface, (10, 10))
pygame.display.update() # Display everything drawn into screen buffer.
fps.tick(2)

# Initialize vesicle size parameters.
sas = 4*pi*(screenradius*screenradius) # Area of sphere at this given screen radius.
sar = sas/points # Divide for area of one Lennard-Jones radius, between points.
rbp = np.sqrt(sar/pi) * 2 # Radius between points, given area.
spacing = rbp-((1/points)*rbp) # Radius between points, ideal spacing.
halfspacing = spacing / 2
quarterspacing = spacing / 4

listlennardjones()

avcutoffs = 0
cutoffcalctime = 10 # Time steps between updates of i,j points within cut-off range.
cutoffupdate = cutoffcalctime # Time cycles per update of cutoff list. +1 to upda
groupcalctime = 10 # Time steps between updates of i,j points within larger group.
groupupdate = groupcalctime # Time cycles per update of group list. +1 to update on startup.

xpop = 0
ypop = 0
zpop = 0
xnew = 0
ynew = 0
znew = 0

# Initialize keyboard entered toggle controls.
paused = False
axeslines = False
pointschange = True
forces = True # Must start True or list of points within cutoff range is not generated.
drawcolors = 2 # 0=none, 1=LennardJones, 2=Connections
pressureChanges = False
drawwaves = True # To enable or disable drawing of signal propagation lines.
drawnumbers = False
holdradius = True
if points < 2000:
outlines = True
else:
outlines = False
screen.fill(Black) # Clear screen of "Intitializing nnnn Points" message.

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Space=pause A = Axes,x=green,y=bluez=yellow. F=force. H=hold Radius. O=outlines.
# P=Pressure. S=save. W=waves. C= color, LJ, # connections. none waves only.
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE: # Space bar = pause.
paused = not paused
if event.key == pygame.K_a: # a = Axis lines in left draw: x=green, y=blue, z=yellow.
axeslines = not axeslines
if event.key == pygame.K_f: # f = Lennard-Jones Force on/off. None = fast stable waves.
forces = not forces
if event.key == pygame.K_h: # h = Hold to (an inner volume) sphere. None = particles drift
holdradius = not holdradius
if event.key == pygame.K_n: # n = Number the points drawn to screen with their index number.
if points < 5000:
drawnumbers = not drawnumbers
if event.key == pygame.K_o: # o = Outline around circles.
outlines = not outlines
if event.key == pygame.K_p: # p = Pressure changes like pounding surf waves, shaken in jar.
pressurechanges = not pressurechanges
if event.key == pygame.K_s: # s = Save new StartupXYZ.txt file, for later automatic restart.
xyzwrite('StartupXYZ.txt')
if event.key == pygame.K_w: # w = Wave signal draw to screen. Inputs turn violet, Outputs white.
drawwaves = not drawwaves
if not drawwaves and drawcolors == 0:
drawcolors = 1 # No blank screen.
if event.key == pygame.K_c: # c = change colors: None, Lennard-Jones, Number of connections.
drawcolors += 1
if drawcolors > 2:
if drawwaves: # Draw nothing while showing waves.
drawcolors = 0
else: # Else toggle to Lennard Jones so that a blank screen is not shown.
drawcolors = 1
if not paused:
myfont = pygame.font.SysFont('arial', 25)
# Pressure changes as in shaking in a jar of water, or caught inside pounding shoreline waves.
vesiclestartR = int(screenradius) # Start size of vesicle sphere, from calculated full size.
vesicleR = vesiclestartR * adjustR
cutoffR = forceCutOff * spacing * adjustR # Distance two points are too far away to exert force.
groupR = groupCutOff * spacing * adjustR
connectR = spacing * connectCutOff * adjustR # Distance two points are touching, bumping each other.
spherewidth = vesiclestartR * 2
ljdiff = 0

# Update positions of points, and rotation to drawn on screen.
if forces:
if pressureChanges:
adjustR, pressureRampDirection = pressureRamp(adjustR, pressureRampDirection)
# adjustR = 1 + random.uniform(-pressure, 0) # Pressure changes of churning water, shaking.
else:
adjustR = 1 # Stay full radius.

cutoffupdate += 1
if cutoffupdate > cutoffcalctime:
cutoffupdate = 0
groupupdate += 1
if groupupdate > groupcalctime:
groupupdate = 0
update_grouplist()
avcutoffs = update_cutofflist()
ljmaxwas = ljmax
ljavr, ljmin, ljmin_i, ljmin_j, ljmax, ljmax_i, atmaxforce = update_positions()
if atmaxforce != 0:
pressureRampDirection = 1
else:
pressureRampDirection = -1

if ljmax < 10: # If activity is low then increase Lennard Jones force.
dt *= 1.1
if dt >= .5:
dt = .5
else: # else decrease.
dt *= .95
if dt <= .001:
dt = .001
# if ljavr < .5: # If activity is low then increase Lennard Jones force.
# dt *= 1.1
# if dt >= .5:
# dt = .5
# else: # else decrease.
# dt *= .95
# if dt <= .001:
# dt = .001
update_connections()
update_angles()
cutoffcalctime = int(1000 / ljmax) * 5 # Timesteps before recalculating distances.
# cutoffcalctime = int(1 / (np.sqrt(abs(ljavr)) / 5) + 1) # Timesteps before recalculating distances.
# cutoffcalctime = int(2 / ljavr) * 5 # Timesteps before recalculating distances.
if cutoffcalctime > 200:
cutoffcalctime = 200
print(timestepnumber, " LJavr=", ljavr, " LJmin=", ljmin, " LJmax=", ljmax,
" dt=", dt, " r=", vesicleR, "of", screenradius)

# Popping out under pressure.
indexmin = ljsum.index(min(ljsum))
ljdiffwas = ljdiff
ljdiff = abs(ljmax - ljmaxwas)
if ljdiff < .00002:
print(ljdiff)
popwait += 1
if ljmax > 10: # If there is enough force on any one point.
pressureRampDirection = -1
if popwait > popwaitcount: # If there has been enough time since last swap
# Pop out the particle with most force around it
popwait = -1 # start new count for waiting again before swapping again
indexmax = ljsum.index(max(ljsum))
xpop = xp[indexmax]
ypop = yp[indexmax]
zpop = zp[indexmax]
azm, ele, rad = vectorsum(indexmin, cutofflist[indexmin])
xo, yo, zo = sphere2cart(azm, ele, rad)
xnew = xp[indexmin] + xo
ynew = yp[indexmin] + yo
znew = zp[indexmin] + zo
xp[indexmax] = xnew
yp[indexmax] = ynew
zp[indexmax] = znew
print(timestepnumber, '>>>>>> One popped out and was sent to one can pop in. <<<<<<')
myfont = pygame.font.SysFont('arial', 26)
textsurface = myfont.render('Pop-Out, 1 Moved', False, White)
screen.blit(textsurface, (height * .8, height * .84)) # height is half the width of screen
myfont = pygame.font.SysFont('arial', 22)
textsurface = myfont.render('From Blue to White Circle', False, White)
screen.blit(textsurface, (height * .76, height * .90)) # height is half the width of screen
pops =+ pops
cutoffupdate = cutoffcalctime # set to next update list of points in cutoff range.
groupupdate = groupcalctime # and set to next update list of points in larger groups.
# Propagate signals.
timestepsignals()

# Label screen:
myfont = pygame.font.SysFont('arial', 27)
textsurface = myfont.render('Front', False, White)
screen.blit(textsurface, (height * .83, height/10)) # height is half the width of screen
textsurface = myfont.render('Back', False, White)
screen.blit(textsurface, (height * 1.06, height/10))
myfont = pygame.font.SysFont('arial', 18)
textsurface = myfont.render(str(points) + ' mol/cell/column', False, White)
screen.blit(textsurface, (5, 0))
textsurface = myfont.render('LJmax: ' + str(round(ljmax, 4)), False, White)
screen.blit(textsurface, (5, height * .04))
textsurface = myfont.render('On: ' + str(signalactivity), False, White)
screen.blit(textsurface, (5, height * .08))
myfont = pygame.font.SysFont('arial', 20)
textsurface = myfont.render('TimeStep ' + str(timestepnumber), False, White)
screen.blit(textsurface, (height * .75, 0))
myfont = pygame.font.SysFont('arial', 18)
textsurface = myfont.render("LJ-Forces=" + str(forces) + " HoldRadius=" + str(holdradius) + " Pressure=" + str(pressureChanges), False, White)
screen.blit(textsurface, (5, height - 24))
if drawcolors == 2:
textsurface = myfont.render('Connections: Red=2 Orng=3 Yell=4 Grn=5 Blu=6 Viol=7 Gray=8', False, White)
if drawcolors == 1:
textsurface = myfont.render('Lennard Jones Potential, White = Extreme Pressure', False, White)
if drawcolors == 0:
textsurface = myfont.render('Shown as Points', False, White)
screen.blit(textsurface, (height, height - 24))
myfont = pygame.font.SysFont('arial', 18)
textsurface = myfont.render('Gary Gaulin, 2023', False, White)
screen.blit(textsurface, (height * 1.7, 4)) # Height is half the width of screen
render() # Draw to screen.

pygame.display.update # Display screen that was being drawn, to monitor display.
# pygame.image.save(screen, 'Images/' + str(timestepnumber) + '.png') # Save image file for making gif video.
fps.tick(50)
screen.fill(Black) # Screen can now be cleared for next draw.
# Rotate sphere a small amount for next draw.
rotation += rotationstep # Add screen rotation amount, for next rendering.
if rotation > radian: # Keep rotation amount in range from 0 to 1 Radian.
rotation = 0
timestepnumber += 1