Code smell - if/else construct - python

I have a list of 9 elements. The first three represent positions, the next velocities, the next forces.
Sometimes I require forces from the array, other times velocities, and other times positions.
So I wrote a function as follows:
def Extractor(alist,quantity):
if quantity=='positions':
x = alist[0]
y = alist[1]
z = alist[2]
return (x,y,z)
elif quantity=='velocities':
vx = alist[3]
vy = alist[4]
vz = alist[5]
tot_v = np.sqrt(vx**2 + vy**2 + vz**2)
return (vx,vy,vz,tot_v)
elif quantity=='forces':
fx = alist[6]
fy = alist[7]
fz = alist[8]
tot_f = np.sqrt(fx**2 + fy**2 + fz**2)
return (fx,fy,fz,tot_f)
else:
print "Do not recognise quantity: not one of positions, velocities, force"
However, this seems like a massive code smell to me due to duplicate code. Is there a nicer, more pythonic way to do this? I'm quite new to OOP, but could I use some kind of class inheritance utilising polymorphism?

Your method violates the Single Responsibility Principle. Consider splitting it like this:
def positionExtractor(alist):
return tuple(alist[0:3])
def velocityExtractor(alist):
velocity = tuple(alist[3:6])
return velocity + (np.sqrt(sum(x**2 for x in velocity)),)
def forcesExtractor(alist):
forces = tuple(alist[6:9])
return forces + (np.sqrt(sum(x**2 for x in forces)),)
You can put them in a dictionary:
extractors = {
'position' : positionExtractor,
'velocity' : velocityExtractor,
'forces' : forcesExtractor}
and use:
result = extractors[quantity](alist)
Here is an example with inheritance. It seems to be over-engineering for such a simple task though:
import numpy as np
class Extractor:
def extract(self, alist):
raise NotImplementedError()
class IndexRangeExtractor(Extractor):
def __init__(self, fromIndex, toIndex):
self.fromIndex = fromIndex
self.toIndex = toIndex
def extract(self, alist):
return tuple(alist[self.fromIndex:self.toIndex])
class EuclideanDistanceExtractorDecorator(Extractor):
def __init__(self, innerExtractor):
self.innerExtractor = innerExtractor
def extract(self, alist):
innerResult = self.innerExtractor.extract(alist)
distance = np.sqrt(sum(x**2 for x in innerResult))
return innerResult + (distance,)
#...
class ExtractorFactory:
def __init__(self):
self.extractors = {
'position':IndexRangeExtractor(0, 3),
'velocity':EuclideanDistanceExtractorDecorator(
IndexRangeExtractor(3, 6)),
'forces':EuclideanDistanceExtractorDecorator(
IndexRangeExtractor(6, 9))}
def createExtractor(self, quantity):
return self.extractors[quantity]
alist = [1,2,3,4,5,6,7,8,9]
ef = ExtractorFactory()
e1 = ef.createExtractor('position')
e2 = ef.createExtractor('velocity')
e3 = ef.createExtractor('forces')
print e1.extract(alist)
print e2.extract(alist)
print e3.extract(alist)

You can start by using an offset to pick out the elements; all but positions need a formula applied too:
_slices = {'positions': slice(3), 'velocities': slice(3, 6), 'forces': slice(6, 9)}
def Extractor(alist, quantity):
try:
a, b, c = alist[_slices[quantity]]
tot = np.sqrt(a**2 + b**2 + c**2)
return a, b, c, tot
except KeyError:
raise ValueError(
"Do not recognise quantity: "
"not one of {}".format(', '.join(_slices)))
This returns a consistent number of values; if calculating the square root for positions is not possible, I'd return 0.0 for the total:
tot = np.sqrt(a**2 + b**2 + c**2) if quantity != 'positions' else 0.0

Why not try something like this:
def extract_position(x,y,z):
return (x, y, z)
def extract_velocities(x,y,z):
return (x, y, z, np.sqrt(x**2 + y**2 + z**2))
def extract_forces(x,y,z):
return (x, y, z, np.sqrt(x**2 + y**2 + z**2))
extractor = { 'positions': extract_position,
'velocities': extract_velocities,
'forces': extract_forces }
try:
print extractor['positions'](1,2,3)
print extractor['unknown'](4,5,6)
except KeyError:
print "Do not recognise quantity: not one of positions, velocities, force"
I prefer to use function pointers to tie data to arbitrary calculations. Also, the dictionary replaced the switch style syntax so this at least feels similar to what you were looking for.
Also you have the same calculation for determining velocities and forces, so you could condense that as well.

Maybe something like:
from operator import itemgetter
def extract(sequence, quantity):
try:
a, b, c = {
'positions': itemgetter(0, 1, 2),
'velocities': itemgetter(3, 4, 5),
'forces': itemgetter(6, 7, 8)
}[quantity](sequence)
return a, b, c, np.sqrt(a**2 + b**2, c**2)
except KeyError as e:
pass # handle no suitable quantity found here
Note that a calculation is always performed instead... keeps the return value consistent as a 4-tuple... unless it's a really expensive calculation, this shouldn't be an issue.

Related

I'm trying to render a 3D Mandelbulb (in Blender) using a Python script, and it doesn't look right

Currently my code returns no errors, and generates a weird looking set of points, totally not a Mandelbulb. I've looked over the formulas multiple times and everything seems right, but I could definitely be overlooking something. Any ideas? Just to note, I'm quite inexperienced with Python (I work in Java a lot though so I get the main ideas). Here's my code:
import bpy
import numpy as np
import math
def mandelbulb(x, y, z, iterations):
c = x + y*1j + z*1j
z = c
r = 0
for i in range(iterations):
r2 = x*x + y*y + z*z
if r2 > 2:
return math.sqrt(r2)
theta = math.atan2(math.sqrt(x*x + y*y), z)
phi = math.atan2(y, x)
r = math.sqrt(abs(x*x + y*y + z*z))
x = r*r*r*r * math.cos(4*theta) * math.cos(4*phi) + c.real
y = r*r*r*r * math.cos(4*theta) * math.sin(4*phi) + c.imag
z = r*r*r*r * math.sin(4*theta)
return 0
def generate_mesh(size, iterations):
vertices = []
for x in np.linspace(-2, 2, size):
for y in np.linspace(-2, 2, size):
for z in np.linspace(-2, 2, size):
value = mandelbulb(x, y, z, iterations)
if value >= 2:
vertices.append((x, y, z))
return vertices, []
def create_mesh_object(vertices, faces, name):
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(vertices, [], faces)
mesh.update()
object = bpy.data.objects.new(name, mesh)
bpy.context.collection.objects.link(object)
def execute(size, iterations):
vertices, faces = generate_mesh(size, iterations)
create_mesh_object(vertices, faces, "Mandelbulb")
class MandelbulbOperator(bpy.types.Operator):
bl_idname = "object.mandelbulb_operator"
bl_label = "Mandelbulb Operator"
bl_options = {'REGISTER', 'UNDO'}
size: bpy.props.IntProperty(
name="Size",
default=32,
min=1,
max=256,
step=1
)
iterations: bpy.props.IntProperty(
name="Iterations",
default=64,
min=1,
max=512,
step=1
)
def execute(self, context):
execute(self.size, self.iterations)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text="Create a 3D Mandelbulb")
layout.prop(self, "size")
layout.prop(self, "iterations")
def register():
bpy.utils.register_class(MandelbulbOperator)
def unregister():
bpy.utils.unregister_class(MandelbulbOperator)
if __name__ == "__main__":
register()
I tried messing with values, such as size and iterations but nothing seemed to change the look of the result, and changing the iterations straight up did nothing. I've also tried using variations on the main Mandelbulb formula but to no avail. Any suggestions are welcome.

Skip duplicated dots based generated by randint() function

I am still learning Python programming and currently struggling to achieve one goal. I got a class Dot that is used to create coordinates and compare them later on. Also, I got a class Player with two other child classes that are inherited from the Parent class.
class Dot:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return {self.x, self.y}
class Player:
def __init__(self, board, enemy):
self.board = board
self.enemy = enemy
def ask(self):
raise NotImplementedError()
def turn(self):
while True:
try:
target = self.ask()
repeat = self.enemy.shoot(target)
return repeat
except BoardExceptionError as e:
print(e)
class Viki(Player):
def ask(self):
answer = Dot(randint(0, 5), randint(0, 5))
time.sleep(3)
print(f'Turn of Viki: {answer.x} {answer.y}')
return answer
class Human(Player):
def ask(self):
while True:
h = input('Your turn: ').split()
if len(h) != 2:
print('Add 2 coordinates...')
continue
x, y = h
if not (x.isdigit()) or not (y.isdigit()):
print('Add numbers from 0 to 6...')
continue
x, y = int(x), int(y)
return Dot(x - 1, y - 1)
What I would like to expect is that class "Viki(Player)" kind of an AI, forcing it to not use the same coordinates(Dots) that are already used(generated) previously. So, every time it should use none used cells on the board.
I understand that it might help in this case logical operators or count function. For example,
Example 1:
a = Dot(1, 2)
b = Dot(1, 3)
c = Dot(1, 4)
abc_list = [Dot(1, 2), Dot(2, 2), Dot(2, 3)]
print(a in abc_list)
Output
True
Example 2:
print(abc_list.count(a))
Output
1
I tried to play with both options but gettings different types of errors when I try to use loops and blocks. I understand that the bottleneck here is my knowledge :) Your help is much appreciated if someone can help me to sort this out. Thanks in advance!
Here is a generator that produces all the dots in random order (no repeats):
from itertools import product
from random import shuffle
def random_dots():
dots = [Dot(*p) for p in product(range(6), repeat=2)]
shuffle(dots)
yield from dots
rd = random_dots()
Now, you can use it in you code:
dot = next(rd)
If pre-generating all dots is not an option because there are too many, you could use the following which is lighter on memory/time:
dots = set()
def random_dot():
while (tpl := (randint(0, 5), randint(0, 5))) in dots:
pass
dots.add(tpl)
return Dot(*tpl)
And use like:
dot = random_dot()

Errors in a class when trying to call it

I have this code consisting of a class and a subclass. The class is Euler forward, while the second one is Eulers midpoint method. These are for solving an ODE (x'=x(1/2-x)). Now it doesn't seem to work because when I am to call the function, by typing:
Euler=H.solve(6)
where the 6 is the amount of steps, I get attributeerror.
AttributeError: 'int' object has no attribute 'size'
Could anyone help me make my code more robust and working so I could plot the values later on, really don't see whats wrong. My code below:
import numpy as np
class H:
def __init__(self, f):
self._f = f
def initial(self, u0):
self._u0 = u0
def solve(self, time_points):
n = time_points.size
self._t = time_points
self._u = np.zeros(n)
self._u[0] = self._u0
for k in range(n-1):
self._k = k
self._u[k+1] = self.advance()
return self._u, self._t
class F(H):
def ad(self):
u = self._u; t = self._t; f = self._f; k = self._k
dt = t[k+1] - t[k]
u_k12 = u[k] + dt/2 * f(u[k], t[k])
return u[k] + dt * f(u_k12, (t[k] + dt/2) )
I think what's wrong is the way you use the class. Initial value is set with initial method (u0), then you give solve method the list of points. You can use np.linscape to generate midpoint.
np.linspace(0, 3, 31) # 30 points evenly spaced between 0 and 3
So it's like this:
def func(x, y):
return x * y
midpoint = np.linspace(0, 3, 31)
F_ = F(func)
F_.initial(6)
F_.solve(midpoint)
Code:
class H:
def __init__(self, f):
self._f = f
def initial(self, u0):
self._u0 = u0
def solve(self, time_points):
n = time_points.size
self._t = time_points
self._u = np.zeros(n)
self._u[0] = self._u0
for k in range(n-1):
self._u[k+1] = self.advance(k)
return self._u, self._t
def advance(self, k):
....
class F(H):
def advance(self, k):
dt = self._t[k+1] + self._t[k]
u_k12 = self._u[k] + dt/2 * self._f(self._u[k], self._t[k])
return self._u[k] + dt * self._f(u_k12, (self._t[k] + dt/2))

How to select parent using roulette wheel?

I'm trying to implement a genetic algorithm for solving the Travelling Salesman Problem (TSP).
I have 2 classes, which are City and Fitness.
I have done the code for initialization.
class City:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, city):
xDis = abs(self.x - city.x)
yDis = abs(self.y - city.y)
distance = np.sqrt((xDis ** 2) + (yDis ** 2))
return distance
def __repr__(self):
return "(" + str(self.x) + "," + str(self.y) + ")"
class Fitness:
def __init__(self, route):
self.route = route
self.distance = None
self.fitness = None
def routeDistance(self):
if self.distance == None:
pathDistance = 0.0
for i in range(0, len(self.route)):
fromCity = self.route[i]
toCity = None
if i+1 < len(self.route):
toCity = self.route[i+1]
else:
toCity = self.route[0]
pathDistance += fromCity.distance(toCity)
self.distance = pathDistance
return self.distance
def routeFitness(self):
if self.fitness == None:
self.fitness = 1 / float(self.routeDistance())
return self.fitness
def selection(population, size=None):
if size== None:
size= len(population)
matingPool = []
fitnessResults = {}
for i in range(0, size):
fitnessResults[i] = Fitness(population[i]).routeFitness()
matingPool.append(random.choice(population))
return matingPool
The code above just randomly selects a parent in the selection method.
My question is: How to code to select a parent using roulette wheels?
You could try this [1, 2]:
from numpy.random import choice
def selection(population, size=None):
if size== None:
size= len(population)
fitnessResults = []
for i in range(0, size):
fitnessResults.append(Fitness(population[i]).routeFitness())
sum_fitness = sum(fitnessResults)
probability_lst = [f/sum_fitness for f in fitnessResults]
matingPool = choice(population, size=size, p=probability_lst)
return matingPool
Read this
So basically, the higher a fitness value, the higher are its chances to be chosen. But that is when high fitness value means a high fitness. But in TSP a lower value of fitness is better so to implement this, we need to implement the concept where probability is indirectly proportional to the fitness value.
Here is something I had implemented in python with some changes
def choose_parent_using_RWS(genes, S):
P = random.uniform(0, S)
for x in genes:
P += get_fitness_value(x)
if P > S:
return x
return genes[-1]
where S is the sum of the inverse of the fitness values of the current population (i.e, 1/f1 + 1/f2 + 1/f3 + ...)
and
get_fitness_value(x) returns the inverse of the distance, just like your routeFitness() function
TeeHee

Crop a collection of lines using another collection of lines

First of all, here is a quick graphical description of what I intend to do. I will use Python, but feel free to use pseudo code in your answers.
I have 2 collections of 2D segments, stored as the following: [ [start_point, end_point], [...] ].
For the first step, I have to detect each segment of the blue collection which is colliding with the black collection. For this I use LeMothe's line/line intersection algorithm.
Then comes my first problem: Having a segment AB which intersects with my line in C, I don't know how to determine using code if I have to trim my segment as AC or as CB , ie: I don't know which part of my segment I need to keep and which one I need to remove.
Then for the second step, I have really no ideas how to achieve this.
Any help would be greatly appreciated, so thank you in advance!
The second step is trivial once you figure what to keep and what not, you just need to keep track of the segments you clipped and see where they were originally joined (e.g. assume that the segments are in order and form a connected line).
On the other hand, given that your black line is in fact a line and not a polygon, in your first step, choosing what is "outside" and what is "inside" seems completely arbitrary; is it possible to close that into a polygon? Otherwise, you may need to artificially create two polygons (one for each side of the line) and then do clipping inside those polygons. You could use something like the Cyrus and Beck line clipping algorithm (see this tutorial for an overview: https://www.tutorialspoint.com/computer_graphics/viewing_and_clipping.htm)
Feel free to use any of the code below as a starting point (you have an intersect function and some useful classes). Implements Sutherland and Hodgman.
class Point2(object):
"""Structure for a 2D point"""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __copy__(self):
return self.__class__(self.x, self.y)
copy = __copy__
def __repr__(self):
return 'Point2(%d, %d)' % (self.x, self.y)
def __getitem__(self, key):
return (self.x, self.y)[key]
def __setitem__(self, key, value):
l = [self.x, self.y]
l[key] = value
self.x, self.y = l
def __eq__(self, other):
if isinstance(other, Point2):
return self.x == other.x and \
self.y == other.y
else:
assert hasattr(other, '__len__') and len(other) == 2
return self.x == other[0] and \
self.y == other[1]
def __ne__(self, other):
return not self.__eq__(other)
def __nonzero__(self):
return self.x != 0 or self.y != 0
def __len__(self):
return 2
class Line2(object):
"""Structure for a 2D line"""
def __init__(self,pt1,pt2):
self.pt1,self.pt2=pt1,pt2
def __repr__(self):
return 'Line2(%s, %s)' % (self.pt1, self.pt2)
class Polygon2(object):
def __init__(self,points):
self.points = points
def __repr__(self):
return '[\n %s\n]' % '\n '.join([str(i) for i in self.points])
def lines(self):
lines = []
e = self.points[-1].copy()
for p in self.points:
lines.append(Line2(e,p))
e = p.copy()
return lines
#return [Line2(a,b) for a,b in zip(self.points,self.points[1:]+[self.points[0]])]
def __copy__(self):
return self.__class__(list(self.points))
copy = __copy__
class Renderer(object):
"""Rendering algorithm implementations"""
def __init__(self,world,img,color=1):
self.world,self.img,self.color=world,img,color
def transform(self,s,r,m,n):
"""Homogeneous transformation operations"""
for i in self.world.points():
j = Matrix3.new_translate(m, n)*Matrix3.new_rotate(r)*Matrix3.new_scale(s)*i
i.x,i.y = j.x,j.y
def clip(self,a,b,c,d):
"""Clipping for the world window defined by a,b,c,d"""
self.clip_lines(a, b, c, d)
self.clip_polygons(a, b, c, d)
def shift(self,a,b,c,d):
"""Shift the world window"""
for i in self.world.points():
i.x -= a
i.y -= b
def clip_lines(self,a,b,c,d):
"""Clipping for lines (i.e. open polygons)"""
clipped = []
for i in self.world.lines:
clipped += [self.clip_lines_cohen_sutherland(i.pt1, i.pt2, a, b, c, d)]
self.world.lines = [i for i in clipped if i]
def clip_polygons(self,a,b,c,d):
"""Clipping for polygons"""
polygons = []
for polygon in self.world.polygons:
new_polygon = self.clip_polygon_sutherland_hodgman(polygon, a, b, c, d)
polygons.append(new_polygon)
self.world.polygons = polygons
def clip_polygon_sutherland_hodgman(self,polygon,xmin,ymin,xmax,ymax):
edges = [Line2(Point2(xmax,ymax),Point2(xmin,ymax)), #top
Line2(Point2(xmin,ymax),Point2(xmin,ymin)), #left
Line2(Point2(xmin,ymin),Point2(xmax,ymin)), #bottom
Line2(Point2(xmax,ymin),Point2(xmax,ymax)), #right
]
def is_inside(pt,line):
# uses the determinant of the vectors (AB,AQ), Q(X,Y) is the query
# left is inside
det = (line.pt2.x-line.pt1.x)*(pt.y-line.pt1.y) - (line.pt2.y-line.pt1.y)*(pt.x-line.pt1.x)
return det>=0
def intersect(pt0,pt1,line):
x1,x2,x3,x4 = pt0.x,pt1.x,line.pt1.x,line.pt2.x
y1,y2,y3,y4 = pt0.y,pt1.y,line.pt1.y,line.pt2.y
x = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))
y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))
return Point2(int(x),int(y))
polygon_new = polygon.copy()
for edge in edges:
polygon_copy = polygon_new.copy()
polygon_new = Polygon2([])
s = polygon_copy.points[-1]
for p in polygon_copy.points:
if is_inside(s,edge) and is_inside(p,edge):
polygon_new.points.append(p)
elif is_inside(s,edge) and not is_inside(p,edge):
polygon_new.points.append(intersect(s,p,edge))
elif not is_inside(s,edge) and not is_inside(p,edge):
pass
else:
polygon_new.points.append(intersect(s,p,edge))
polygon_new.points.append(p)
s = p
return polygon_new
def clip_lines_cohen_sutherland(self,pt0,pt1,xmin,ymin,xmax,ymax):
"""Cohen-Sutherland clipping algorithm for line pt0 to pt1 and clip rectangle with diagonal from (xmin,ymin) to (xmax,ymax)."""
TOP = 1
BOTTOM = 2
RIGHT = 4
LEFT = 8
def ComputeOutCode(pt):
code = 0
if pt.y > ymax: code += TOP
elif pt.y < ymin: code += BOTTOM
if pt.x > xmax: code += RIGHT
elif pt.x < xmin: code += LEFT
return code
accept = False
outcode0, outcode1 = ComputeOutCode(pt0), ComputeOutCode(pt1)
while True:
if outcode0==outcode1==0:
accept=True
break
elif outcode0&outcode1:
accept=False
break
else:
#Failed both tests, so calculate the line segment to clip from an outside point to an intersection with clip edge.
outcodeOut = outcode0 if not outcode0 == 0 else outcode1
if TOP & outcodeOut:
x = pt0.x + (pt1.x - pt0.x) * (ymax - pt0.y) / (pt1.y - pt0.y)
y = ymax
elif BOTTOM & outcodeOut:
x = pt0.x + (pt1.x - pt0.x) * (ymin - pt0.y) / (pt1.y - pt0.y)
y = ymin
elif RIGHT & outcodeOut:
y = pt0.y + (pt1.y - pt0.y) * (xmax - pt0.x) / (pt1.x - pt0.x);
x = xmax;
elif LEFT & outcodeOut:
y = pt0.y + (pt1.y - pt0.y) * (xmin - pt0.x) / (pt1.x - pt0.x);
x = xmin;
if outcodeOut == outcode0:
pt0 = Point2(x,y)
outcode0 = ComputeOutCode(pt0)
else:
pt1 = Point2(x,y)
outcode1 = ComputeOutCode(pt1);
if accept:
return Line2(pt0,pt1)
else:
return False
I think what you'll need to do is find a line from the center of the blue object to the line segment in question. If that new line from the center to the segment AB or BC hits a black line on its way to the blue line segment, then that segment is outside and is trimmed. You would want to check this at a point between A and B or between B and C, so that you don't hit the intersection point.
As for the python aspect, I would recommend defining a line object class with some midpoint attributes and a shape class that's made up of lines with a center attribute, (Actually come to think of it, then a line would count as a shape so you could make line a child class of the shape class and preserve code), that way you can make methods that compare two lines as part of each object.
line_a = Line((4,2),(6,9))
line_b = Line((8,1),(2,10))
line_a.intersects(line.b) #Could return Boolean, or the point of intersection
In my mind that just feels like a really comfortable way to go about this problem since it lets you keep track of what everything's doing.

Categories