Class Design utilising numpy arrays - python

I have a class which accepts three arguments:
class Spheroid(object):
def __init__(self, shortaxis, longaxis, height):
self.shortax = shortaxis
self.longax = longaxis
self.h = height
self.alpha = longaxis/shortaxis
This is fine, but sometimes I like to do something like:
heights = np.linspace(0,1,100)
a = Spheroid(1.0,2.0,heights)
a.h
which gives me an array of the heights.
The longaxis and shortaxis are input parameters which I measure, but sometimes I'd like to do something like
class Spheroid(object):
def __init__(self, alpha, height):
self.alpha = alpha
self.h = height
alphas = np.linspace(0,3,30)
b = Spheroids(alphas, 0.5)
b.alpha
but I want to keep the longax and shortax as input parameters so I don't have to calculate alpha by hand.
Any ideas on how I can design this class to do what I want?

Related

Pythonic way to pass a method to another function

I'm unsure the best way to do the following. That is, I'm not sure if I should have a parent class UniSamplingStrategy and child classes UniformSampling, and RandomSampling. Or should I just have UniSamplingStrategy and have the types of samplings as methods? For example, this is what I did:
import numpy as np
## make a base class w/ child classes instead?
class UniSamplingStrategy():
def __init__(self,
left=0,
right=0,
num_samples=0,
cluster_center=None,
defined_array=[0]
):
self._left = left
self._right = right
self._num_samples = num_samples
self._cluster_center = cluster_center
self._defined_array = defined_array
# uniform sampling
def uniform_sampling(self):
return np.linspace(start=self._left,
stop=self._right,
num=self._num_samples,
endpoint=True,
dtype=np.float32)
# random spacing
def clustered_sampling(self):
return np.random.normal(loc=self._clust_center,
scale=(self._right - self._left)/4,
size=self._num_samples)
What I want to do with this class (or perhaps classes, if I need to rewrite for good python) is pass a sampling strategy to my data_generation method.
def data_generation(noise_scale,
sampling_strategy,
test_func,
noise_type
):
x_samples = sampling_strategy
y_samples = test_func(x=x_samples)
if noise_type is not None:
_, y_samples_noise = noise_type(x=x_samples, scale=noise_scale)
y_samples = y_samples + y_samples_noise
return x_samples, y_samples
def test_func(x):
return (np.cos(x))**2/((x/6)**2+1)
def hmskd_noise(x, scale):
scales = scale
return scales, np.random.normal(scale=scale, size=x.shape[0])
So that ideally, I could try different test functions, noise, and sampling schemes. Where I could write function calls like:
x_true, y_true = data_generation(sampling_strategy=uniform_sampling(left=0, right=10, num_samples=1000)
test_func = test_func,
noise_type=None,
noise_scale = 0)
x_obs, y_obs = data_generation(sampling_strategy=clustered_sampling(clustered_center=5, left=0, right=10, num_samples = 20),
test_func = test_func,
noise_type=hmskd_noise,
noise_scale=0.2)
Essentially, I'm interested in the best way to pass a sampling strategy to data_generation when each method can have different parameters to pass (e.g., see uniform_sampling and clustered_sampling parameters).
Thanks for your time :)
For example, you can have a set of classes with __call__ method. Like
class UniformSampling:
def __init__(self,
left=0,
right=0,
num_samples=0,
cluster_center=None,
defined_array=[0]
):
self._left = left
self._right = right
self._num_samples = num_samples
self._cluster_center = cluster_center
self._defined_array = defined_array
def __call__(self, arg1, arg2):
return np.linspace(start=self._left,
stop=self._right,
num=self._num_samples,
endpoint=True,
dtype=np.float32)
Then you can pass instantiated object to data_generation as
x_true, y_true = data_generation(sampling_strategy=UniformSampling(left=0, right=10, num_samples=1000),
test_func = test_func,
noise_type=None,
noise_scale = 0)

Adding name and history attribute to 2D particle simulation

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

Python Object initialization,_init_ method

How Can I create class if i have to create object for my class like below.
Obj1 = Class()
Obj2 = Class(para1,para2,para3)
This is related to a task that i need to complete just started learning Python.
I tried construction overloading but it seems to not work in Python .Can anyone tell me how can i achieve this or it is technically wrong to have both line in one code.
You can use *args or **kwargs
class Class1:
def __init__(self, *args):
pass
obj1 = Class1()
obj2 = Class1(para1,para2,para3)
or
class Class1:
def __init__(self, **kwargs):
pass
obj1 = Class1()
obj2 = Class1(para1=para1,para2=para2,para3=para3)
Refer this to learn more about *args and **kwargs
If you set default values like length = 80, you don't have to set them. But if you set the values, it ll be set as you wish. The following code demonstrates almost what you want.
class Rectangle:
def __init__(self, length = 80, breadth = 60, unit_cost=100):
self.length = length
self.breadth = breadth
self.unit_cost = unit_cost
def get_perimeter(self):
return 2 * (self.length + self.breadth)
def get_area(self):
return self.length * self.breadth
def calculate_cost(self):
area = self.get_area()
return area * self.unit_cost
# r = Rectangle() <- this returns with default values
# breadth = 120 cm, length = 160 cm, 1 cm^2 = Rs 2000
r = Rectangle(160, 120, 2000)
print("Area of Rectangle: %s cm^2" % (r.get_area()))
print("Cost of rectangular field: Rs. %s " %(r.calculate_cost()))

Python does not select the right part of a function with if clauses

I am new in Python and I am facing a kind of silly problem since already a while. I have a simple function that calculates the self load of an object.
In case the object is of types 'la' the input requests two thickness of this object. I was thinking that the function has to make a vector for each input.
E.g. number of layers =2
the result is a vector of two elements with a thickness chosen by the
user.
thicknesses =[4 6]
The problem is that Python does not select the right operation to do with the function and it returns the the output of another part of this function. Python returns the value of self_load when types !='la'. On top of that Python does not consider another parameter of the if clauses number_edges.
Why?
Here below you can find the function and the main program with already pre selected the right input values.
Is anybody can help me?
# FUNCTION
class load:
def __init__(self, thicknesses, width, height, thick, types, number_edges, inclination=90, density=2500):
self.width = width
self.height = height
self.thick = thick
self.types = types
self.number_edges = number_edges
self.density = density
self.thicknesses = thicknesses
self.inclination = inclination
self.a = min(self.width, self.height)
self.b = max(self.width, self.height)
def self_load(self, gravity=9.81):
if self.types == 'la':
for i in self.thicknesses:
if self.number_edges == 'four':
G = np.array(self.density * self.thicknesses * gravity * math.cos(math.radians(self.inclination)) / 1000) # Self load in kN/m^2
self.self_load = G
# print(self.self_load)
else :
G = np.array(self.a * self.density * self.thicknesses * gravity * math.cos(math.radians(self.inclination)) / 1000) # Self load in kN/m
self.self_load = G
# print(self.self_load)
else :
if self.number_edges == 'four':
G = self.density * self.thick * gravity * math.cos(math.radians(self.inclination)) / 1000 # Self load in kN/m^2
self.self_load = G
#print(self.self_load)
else :
G = self.a * self.density * self.thick * gravity * math.cos(math.radians(self.inclination)) / 1000 # Self load in kN/m
self.self_load = G
print(self.self_load)
# MAIN
import function_try
"""
INPUT
"""
width = 0.66
height = 2.2
thick = 0.006
inclination = 45
number_edges = 'four'
types = 'la'
if types == 'la':
n_layers = 2 # Enter number of layers
thicknesses = [] # Create a vector
for i in range(0, n_layers): # Set up loop to run n times
number = int(input('Enter the thickness: '))
thicknesses.append(number) # append to our_list
else:
thicknesses = 0
test = function_try.load(thicknesses, width, height, thick, number_edges, types, inclination)
test.self_load()
print('Self load is:', test.self_load)

Creating Classes with multiple mutable vector attributes in python

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)

Categories