Are the classmethods being used correctly?
I am working on a program to create data input for an 3-D N-body problem. The goal is to create a uniform density sphere with 50000 particles. Each particle class instance must have a mass, position and velocity. The position vector must be in spherical so when the instance of a particle is created it is within a sphere of radius 1. The velocity must be randomized in 3-directions. This will be changed later by adding an orbital velocity. All the data will later be exported into 3 lists masses, position and velocity all in Cartesian coordinates.
I am having trouble creating the particles with such attributes.
The first run of code was:
import math
import numpy as np
class Particle:
def__init__(self,mass,position,velocity):
self.mass = 1/50000
self.position = position
self.velocity=velocity
def position(self):
self.position = (self.r, self.theta, self.phi)
#classmethod
def set_r(cls, r):
cls.r = np.rand.uniform(0,1)
#classmethod
def set_theta(cls, theta):
cls.theta = np.rand.uniform(-(math.pi)/2 ,(math.pi)/2)
#classmethod
def set_phi(cls, phi):
cls.phi = np.rand.uniform(0,2*math.pi)
def velocity(self):
self.velocity = (self.Vx, self.Vy, self.Vz)
#classmethod
def set_Vx(cls, Vx):
cls.Vx = np.rand.uniform(0,0.001)
#classmethod
def set_Vy(cls, Vy):
cls.Vy = np.rand.uniform(0,0.001)
#classmethod
def set_Vz(cls, Vz):
cls.Vz = np.rand.uniform(0,0.001)
After talking to a friend in the CS department the code was edited to:
import math
import numpy as np
class Particle():
def __init__(self,mass,position,velocity):
self.mass = 1/50000
self.position = position[]
self.velocity = velocity[]
#classmethod
def getPosition(cls):
return [cls.r, cls.theta, cls.phi]
#classmethod
def set_r(cls, r):
cls.position[0] = np.rand.uniform(0,1)
#classmethod
def set_theta(cls, theta):
cls.position[1] = np.rand.uniform(-(math.pi)/2 ,(math.pi)/2)
#classmethod
def set_phi(cls, phi):
cls.position[2] = np.rand.uniform(0,2*math.pi)
def getVelocity(cls):
return [cls.Vx, cls.Vy, cls.Vz]
#classmethod
def set_Vx(cls, Vx):
cls.velocity[0] = np.rand.uniform(0,0.001)
#classmethod
def set_Vy(cls, Vy):
cls.velocity[1] = np.rand.uniform(0,0.001)
#classmethod
def set_Vz(cls, Vz):
cls.velocity[2] = np.rand.uniform(0,0.001)
Do I need to define the parts of the vectors in the init and then use a classmethod to create the arrays to be used and changed later?
EDIT 1: The class will be ran trough a for loop to create 50000 particles each with the same mass (normalized to 1/50000), a position vector, and a velocity vector. So exported to a .dat file in a list
If I'm understanding correctly, I don't believe you need classmethods here, rather you want to deal with each Particle individually. If I'm correct, I believe you're looking for a class that each instance knows it's own mass, position and velocity. I made a class that resembles yours but I used namedtuples to represent a position, and velocity.
import math
import numpy as np
from collections import namedtuple
Position = namedtuple('Position', ('r', 'theta', 'phi'))
Velocity = namedtuple('Velocity', ('Vx', 'Vy', 'Vz'))
class Particle():
#With 50,000 instances being created, I suggest using __slots__ if possible.
#This will cut down some on memory consumption.
__slots__ = ('mass', 'position', 'velocity')
def __init__(self, *args, **kwargs):
self.mass = kwargs.get('mass', None)
self.position = kwargs.get('position', None)
self.velocity = kwargs.get('velocity', None)
#Note! This will automatically set random values if any
#of mass, position, velocity are None when initialized
#so this may need to be altered if this is undesired
#this is just a skeleton example and if it works for you it works
if not any((self.mass, self.position, self.velocity)):
self.setup_random()
def setup_random(self):
self.mass = 1/1500
self.position = Position(
r = np.random.uniform(0,1),
theta = np.random.uniform(-(math.pi)/2 ,(math.pi)/2),
phi = np.random.uniform(0,2*math.pi)
)
self.velocity = Velocity(
Vx = np.random.uniform(0,0.001),
Vy = np.random.uniform(0,0.001),
Vz = np.random.uniform(0,0.001)
)
def set_r(self, r):
self.position = self.position.replace(r = r)
def set_theta(self, theta):
self.position = self.position.replace(theta = theta)
def set_phi(self, phi):
self.position = self.position.replace(phi = phi)
def set_Vx(self, Vx):
self.velocity = self.velocity.replace(Vx = Vx)
def set_Vy(self, Vy):
self.velocity = self.velocity.replace(Vy = Vy)
def set_Vz(self, Vz):
self.velocity = self.velocity.replace(Vz = Vz)
def __str__(self):
return('Mass: {}\nPosition: {}\nVelocity: {}'.format(
self.mass,
self.position,
self.velocity))
def __repr__(self):
return('Mass: {}\nPosition: {}\nVelocity: {}'.format(
self.mass,
self.position,
self.velocity))
From here you can make as many particles as needed just using Particle()
p = Particle()
print(p)
And this prints:
Mass: 0.0006666666666666666
Position: Position(r=0.8122849235862195, theta=-0.060787026289457646, phi=3.415049614503205)
Velocity: Velocity(Vx=0.0006988289817776562, Vy=0.00040214068163074246, Vz=0.0003347218438727625)
You can get a value very easily thanks to the namedtuples as well:
print(p.position.r)
#0.8122849235862195
And you can make a particle using pre-defined values like the following:
p = Particle(
mass = 2/5000,
position = Position(r=1, theta = 2, phi = 3),
velocity = Velocity(Vx = 4, Vy = 5, Vz = 6))
print(p)
Results:
Mass: 0.0004
Position: Position(r=1, theta=2, phi=3)
Velocity: Velocity(Vx=4, Vy=5, Vz=6)
You will still need the setter methods to set individual values such as r, theta... as tuples are immutable, although you can set a completely new position easily as well Ex:
#to set an individual value
p.set_r(1)
#setting a whole new position/velocity
p.position = Position(r = 1, theta = 2, phi = 3)
#or
p.position = Position(r = p.position.r, theta = 2, phi = 3)
#as an example
If you want to use a different collection type or anything feel free to do so I just figured namedtuples fit well here.
Edit
To allow for loading and unloading from a data file; you can make to_json and from_json methods.
Let's say your data for one Particle looks like:
d = {'mass': 0.0006666666666666666,
'r': 0.8122849235862195,
'theta': -0.060787026289457646,
'phi': 3.415049614503205,
'Vx': 0.0006988289817776562,
'Vy': 0.00040214068163074246,
'Vz': 0.0003347218438727625
}
Your methods would look like this:
def to_json(self):
json_particle = {'mass': self.mass}
json_particle.update(dict(self.position._asdict()))
json_particle.update(dict(self.velocity._asdict()))
return json_particle
#here we finally use #classmethod
#as we are creating a new instance of the class
#classmethod
def from_json(cls, data):
pos = Position(r = data['r'], theta = data['theta'], phi = data['phi'])
vel = Velocity(Vx = data['Vx'], Vy = data['Vy'], Vz = data['Vz'])
return cls(data['mass'], pos, vel)
Related
Suppose I want to create a class called "Point". Suppose I only ever plan on making two points because that's important. I want to create a method that can be called by either instance or "point" that gives the distance between the two points. How would I go about doing that?
This is what my code looks like.
import math
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self): # Just filler code
return math.sqrt((self_2.x - self_1.x)**2+(self_2.y - self_1.y)**2) # Just filler code
point_1 = Point(0,0)
point_2 = Point(3,4)
print(point_1.distance()) # Should return 5.0
print(point_2.distance()) # Should return 5.0
Obviously I know what I made here doesn't work, but I'm just trying to give an idea of what the print statement should do. How would I go about doing this?
import math
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self,other):
return math.sqrt((self.x - other.x)**2+(self.y - other.y)**2)
point_1 = Point(0,0)
point_2 = Point(3,4)
print(point_1.distance(point_2)) # Should return 5.0
print(point_2.distance(point_1)) # Should return 5.0
You should use a second class that specifically represents two points.
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def distance_to(self, other):
return abs(complex(self.x, self.y) - complex(other.x, other.y))
class PointPair:
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def distance(self):
return self.p1.distnace_to(self.ps2)
point_1 = Point(0,0)
point_2 = Point(3,4)
pair = PointPair(point_1, point_2)
print(pair.distance()
or a regular function
def distance(p1, p2):
return abs(complex(p1.x, p1.y) - complex(p2.x, p2.y))
print(distance(point_1, point_2))
I am attempting to create a program which simulates particles colliding in a 2D box, but each particle is labeled with a random 5 character string name and each collision is tracked in a list along each particle. So after the simulation, I would like a list from each particle listing which particles it has hit. I have forked this great simulation https://github.com/xnx/collision and added the name and history attributes to the particle class. However, whenever I attempt to access .name or .history, my kernel dies. The output says:
Kernel died, restarting
Restarting kernel...
The failure happens in the handle_collisions function (line 197), or whenever I try to access the history or the name, so there must be something wrong in my implementation of the name and history attributes. I have also tried to instantiate name and history in the init_particles function instead of place_particles but that had the same results. I'm not exactly sure how to correctly implement them. Thanks for your help.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation
from itertools import combinations
import random
import string
class Particle:
"""A class representing a two-dimensional particle."""
def __init__(self, x, y, vx, vy, name, history, radius=0.01, styles=None):
"""Initialize the particle's position, velocity, name, history and radius.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Circle patch constructor.
"""
self.r = np.array((x, y))
self.v = np.array((vx, vy))
self.radius = radius
self.mass = self.radius**2
self.styles = styles
if not self.styles:
# Default circle styles
self.styles = {'edgecolor': 'b', 'fill': False}
# For convenience, map the components of the particle's position and
# velocity vector onto the attributes x, y, vx and vy.
#property
def x(self):
return self.r[0]
#x.setter
def x(self, value):
self.r[0] = value
#property
def y(self):
return self.r[1]
#y.setter
def y(self, value):
self.r[1] = value
#property
def vx(self):
return self.v[0]
#vx.setter
def vx(self, value):
self.v[0] = value
#property
def vy(self):
return self.v[1]
#vy.setter
def vy(self, value):
self.v[1] = value
#property
def history(self):
return self.history
#history.setter
def history(self,value):
self.history=value
#property
def name(self):
return self.name
#name.setter
def name(self,value):
self.name=value
def overlaps(self, other):
"""Does the circle of this Particle overlap that of other?"""
return np.hypot(*(self.r - other.r)) < self.radius + other.radius
def draw(self, ax):
"""Add this Particle's Circle patch to the Matplotlib Axes ax."""
circle = Circle(xy=self.r, radius=self.radius, **self.styles)
ax.add_patch(circle)
return circle
def advance(self, dt):
"""Advance the Particle's position forward in time by dt."""
self.r += self.v * dt
class Simulation:
"""A class for a simple hard-circle molecular dynamics simulation.
The simulation is carried out on a square domain: 0 <= x < 1, 0 <= y < 1.
"""
ParticleClass = Particle
def __init__(self, n, radius=0.01, styles=None):
"""Initialize the simulation with n Particles with radii radius.
Each particle is initialized with a 10 letter string name and an empty history.
radius can be a single value or a sequence with n values.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Circle patch constructor when drawing
the Particles.
"""
self.init_particles(n, radius, styles)
self.dt = 0.01
def place_particle(self, rad, styles):
# Choose x, y so that the Particle is entirely inside the
# domain of the simulation.
x, y = rad + (1 - 2*rad) * np.random.random(2)
# Choose a random velocity (within some reasonable range of
# values) for the Particle.
vr = 0.1 * np.sqrt(np.random.random()) + 0.05
vphi = 2*np.pi * np.random.random()
vx, vy = vr * np.cos(vphi), vr * np.sin(vphi)
name = self.assignname
history = []
particle = self.ParticleClass(x, y, vx, vy, name, history, rad, styles)
# Check that the Particle doesn't overlap one that's already
# been placed.
for p2 in self.particles:
if p2.overlaps(particle):
break
else:
self.particles.append(particle)
return True
return False
def assignname(self):
letters = string.ascii_lowercase
name=''.join(random.choice(letters) for i in range(5))
return name
def init_particles(self, n, radius, styles=None):
"""Initialize the n Particles of the simulation.
Positions and velocities are chosen randomly; radius can be a single
value or a sequence with n values.
"""
try:
iterator = iter(radius)
assert n == len(radius)
except TypeError:
# r isn't iterable: turn it into a generator that returns the
# same value n times.
def r_gen(n, radius):
for i in range(n):
yield radius
radius = r_gen(n, radius)
self.n = n
self.particles = []
for i, rad in enumerate(radius):
# Try to find a random initial position for this particle.
while not self.place_particle(rad, styles):
pass
def change_velocities(self, p1, p2):
"""
Particles p1 and p2 have collided elastically: update their
velocities.
"""
m1, m2 = p1.mass, p2.mass
M = m1 + m2
r1, r2 = p1.r, p2.r
d = np.linalg.norm(r1 - r2)**2
v1, v2 = p1.v, p2.v
u1 = v1 - 2*m2 / M * np.dot(v1-v2, r1-r2) / d * (r1 - r2)
u2 = v2 - 2*m1 / M * np.dot(v2-v1, r2-r1) / d * (r2 - r1)
p1.v = u1
p2.v = u2
def handle_collisions(self):
"""Detect and handle any collisions between the Particles.
When two Particles collide, they do so elastically: their velocities
change such that both energy and momentum are conserved.
"""
# We're going to need a sequence of all of the pairs of particles when
# we are detecting collisions. combinations generates pairs of indexes
# into the self.particles list of Particles on the fly.
#particles share history when they collide
pairs = combinations(range(self.n), 2)
for i,j in pairs:
if self.particles[i].overlaps(self.particles[j]):
self.change_velocities(self.particles[i], self.particles[j])
#FAILS HERE
#self.particles[i].history.append(self.particles[j].name)
#self.particles[j].history.append(self.particles[i].name)
def handle_boundary_collisions(self, p):
"""Bounce the particles off the walls elastically."""
if p.x - p.radius < 0:
p.x = p.radius
p.vx = -p.vx
if p.x + p.radius > 1:
p.x = 1-p.radius
p.vx = -p.vx
if p.y - p.radius < 0:
p.y = p.radius
p.vy = -p.vy
if p.y + p.radius > 1:
p.y = 1-p.radius
p.vy = -p.vy
def apply_forces(self):
"""Override this method to accelerate the particles."""
pass
def advance_animation(self):
"""Advance the animation by dt, returning the updated Circles list."""
for i, p in enumerate(self.particles):
p.advance(self.dt)
self.handle_boundary_collisions(p)
self.circles[i].center = p.r
self.handle_collisions()
self.apply_forces()
return self.circles
def advance(self):
"""Advance the animation by dt."""
for i, p in enumerate(self.particles):
p.advance(self.dt)
self.handle_boundary_collisions(p)
self.handle_collisions()
self.apply_forces()
def init(self):
"""Initialize the Matplotlib animation."""
self.circles = []
for particle in self.particles:
self.circles.append(particle.draw(self.ax))
return self.circles
def animate(self, i):
"""The function passed to Matplotlib's FuncAnimation routine."""
self.advance_animation()
return self.circles
def setup_animation(self):
self.fig, self.ax = plt.subplots()
for s in ['top','bottom','left','right']:
self.ax.spines[s].set_linewidth(2)
self.ax.set_aspect('equal', 'box')
self.ax.set_xlim(0, 1)
self.ax.set_ylim(0, 1)
self.ax.xaxis.set_ticks([])
self.ax.yaxis.set_ticks([])
def save_or_show_animation(self, anim, save, filename='collision.mp4'):
if save:
Writer = animation.writers['ffmpeg']
writer = Writer(fps=10, bitrate=1800)
anim.save(filename, writer=writer)
else:
plt.show()
def do_animation(self, save=False, interval=1, filename='collision.mp4'):
"""Set up and carry out the animation of the molecular dynamics.
To save the animation as a MP4 movie, set save=True.
"""
self.setup_animation()
anim = animation.FuncAnimation(self.fig, self.animate,
init_func=self.init, frames=800, interval=interval, blit=True)
self.save_or_show_animation(anim, save, filename)
if __name__ == '__main__':
nparticles = 20
radii = .02
styles = {'edgecolor': 'C0', 'linewidth': 2, 'fill': None}
sim = Simulation(nparticles, radii, styles)
sim.do_animation(save=False)
I see two immediate problems in your code.
First: you did add history as a Particle.__init__ parameter but you never initialize the property itself.
Add something like this:
def __init__(self, x, y, vx, vy, name, history, radius=0.01, styles=None):
self._history = history
And bigger problem: you have an infinite recursion in your #property definition:
#property
def history(self):
return self.history
#history.setter
def history(self,value):
self.history=value
So in your getter you called history you call itself return self.history which will loop itself until program will crash.
Rename internal property to _history:
#property
def history(self):
return self._history
#history.setter
def history(self,value):
self._history=value
So i have here my main program (which i absolutely can't make any changes because this is how our instructor wants it to be run):
from class_point import Point
from class_polygon import Polygon
pt1 = Point(0,0)
pt2 = Point(0,4)
pt3 = Point(3,0)
polygon1 = Polygon([pt1,pt2,pt3]) #yes, the class Polygon will be initialized using a list
print(polygon1.get_perimeter())
So basically, i have two separate files containing the definitions of class Polygon and class Point.
This is my code for class Point which has the function for calculating the distance between two given points:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, second):
x_d = self.x - second.x
y_d = self.y - second.y
return (x_d**2 + y_d**2) **0.5
And this is my code for class Polygon which will use the defined points and the distance function to calculate the perimeter of my polygon:
class Polygon():
def __init__(self, points):
self.points = points
def __len__(self):
len_points = len(self.points)
return len_points
def get_perimeter(self,points,len_points):
perimeter = 0
for i in range(0, len_points):
pt1 = self.points[i]
pt2 = self.points[i+1]
perimeter += pt1.distance(pt2)
if i + 1 == len_points:
perimeter += points[-1].distance(points[0])
else:
continue
return perimeter
But whenever i try to run the code, i get the error:
File "C:/Users/Dust/Desktop/polygon_trial.py", line 8, in <module>
print(polygon1.get_perimeter())
TypeError: get_perimeter() missing 2 required positional arguments: 'points' and 'len_points'
This should work
class Polygon():
def __init__(self, points):
self.points = points
def __len__(self):
return len(self.points)
def get_perimeter(self):
perimeter = 0
for i in range(0, len(self.points)):
pt1 = self.points[i]
pt2 = self.points[i+1]
perimeter += pt1.distance(pt2)
if i + 1 == len(self.points):
perimeter += self.points[-1].distance(self.points[0])
else:
continue
return perimeter
I have the following code:
class Point:
"""Two-Dimensional Point(x, y)"""
def __init__(self, x=0, y=0):
# Initialize the Point instance
self.x = x
self.y = y
#property
def magnitude(self):
# """Return the magnitude of vector from (0,0) to self."""
return math.sqrt(self.x ** 2 + self.y ** 2)
def __str__(self):
return 'Point at ({}, {})'.format(self.x, self.y)
def __repr__(self):
return "Point(x={},y={})".format(self.x, self.y)
The class has a function called magnitude. I want to create a function which can tell the magnitude distance between two points. The following is an expected output:
point1 = Point(2, 3)
point2 = Point(5, 7)
print(point1.magnitude)
3.605551275463989
print(point1.distance(point2))
5.0
I tried doing something like this:
#classmethod
def distance(self, self1, self2):
pointmag1 = self1.magnitude
pointmag2 = self2.magnitude
bsmag = pointmag1 - pointmag2
bsmag2 = pointmag2 - pointmag1
if pointmag2 > pointmag1:
return combsmag2
else:
return combmag
This code always gives me the TypeError: distance() missing 1 required positional argument: 'self2'. Any way to fix this?
You have a few options. If you'd like to explicitly pass both Point instances to your distance function, define it outside the Point class:
def distance(point1, point2):
pointmag1 = point1.magnitude
pointmag2 = point2.magnitude
...
and call it with:
point1 = Point(2, 3)
point2 = Point(5, 7)
distance(point1, point2)
If you'd like to keep the interface you wrote in your example though - point1.distance(point2) - then you need distance to be an instance method, not a class method:
class Point:
# all your other code still here
def distance(self, other):
pointmag1 = self.magnitude
pointmag2 = other.magnitude
...
In this case self is point1, and now other is point2.
(Note your distance method won't work as is, the variables combsmag and combsmag2 are never defined, but that's unrelated to the error you're asking about.)
Points are defined by giving the x and y coordinates. The distance between two points can be calculated by using the distance_from method. Points should have publicly available at least the following properties x, y and distance_from.
Circles are defined by giving the center point and the radius. Circle has the method is_inside that answers if the given point is inside the circle or not. Circles should have publicly available at least the following properties center, radius and is_inside.
The issue here is that i can't initiate my parent and base classes.
Side note: Funtions and classes distance_from, Circle, circle.is_inside only take one p.
import math
class Point():
def __init__(self, xpoint, ypoint):
self.xpoint = xpoint
self.ypoint = ypoint
def distance_from(xpoint, ypoint):
distance = math.sqrt(((p1.xpoint-p2.xpoint)**2)+((p1.ypoint-p2.ypoint)**2))
print(distance)
class Circle(Point):
def __init__(self, xpoint, ypoint, r):
super(Circle, self).__init__(xpoint, ypoint)
self.r = r
def is_inside(xpoint, ypoint):
if self.r > distance:
print("False")
else:
print("True")
p1 = Point(0, 0)
p2 = Point(2, 4)
p1.distance_from(p2)
circle = Circle(p2,4)
circle.is_inside(p1)
Change your coe to this: I added comments to your mistakes
import math
class Point(object): # Add object here
def __init__(self, xpoint, ypoint):
self.xpoint = xpoint
self.ypoint = ypoint
def distance_from(self, xpoint, ypoint): # Missing self here
distance = math.sqrt(((p1.xpoint-p2.xpoint)**2)+((p1.ypoint-p2.ypoint)**2))
print(distance)
class Circle(Point):
def __init__(self, xpoint, ypoint, r):
super(Circle, self).__init__(xpoint, ypoint)
self.r = r
def is_inside(self, point): # Missing self here
if self.r > self.distance_from(point.xpoint, point.ypoint): # You'll have to callculate distance here
print("False")
else:
print("True")
p1 = Point(0, 0)
p2 = Point(2, 4)
p1.distance_from(p1.xpoint, p1.ypoint) # Missing points
c = Circle(p1.xpoint, p1.ypoint, 4) # Missing points
c.is_inside(p2)
If you don't want to use super().init use this:
Point.__init__(self, xpoint, ypoint) # Change super to the superclass name. Add self to the init method
Some notes:
Member functions always have self as the first argument.
Member functions should only access to variables accessible through self or the fuction parameters, never global scope.
import math
class Point(object):
def __init__(self, xpoint, ypoint):
self.xpoint = xpoint
self.ypoint = ypoint
# Here you where missing self and you were accessing p2 that exists in global scope.
def distance_from(self, another_point):
distance = math.sqrt(((self.xpoint-another_point.xpoint)**2)+((self.ypoint-another_point.ypoint)**2))
print(distance)
class Circle(Point):
def __init__(self, xpoint, ypoint, r):
super(Circle, self).__init__(xpoint, ypoint)
self.r = r
def is_inside(self, another_point):
# you forgot to calculate the distance
if self.distance_from(another_point) < self.r:
print("True")
else:
print("False")
p1 = Point(0, 0)
p2 = Point(2, 4)
p1.distance_from(p2)
#create a circle centered at 2,4 with radius 4
circle = Circle(2, 4, 4)
circle.is_inside(p1) # returns "True"
When calling __init__ manually you must provide the self argument.