I am currently trying to write a Python ROS program which can be executed as a ROS node (using rosrun) that implements the defs declared in a separate Python file arm.py (available at: https://github.com/nortega1/dvrk-ros/...). The program initially examines the current cartesian position of the arm. Subsequently, when provided with a series of points that the arm must pass through, the program calculates a polynomial equation and given a range of x values the program evaluates the equation to find the corresponding y values.
Within the arm.py file there is a publisher set_position_cartesian_pub that sets the Cartesian position of the arm as follows:
self.__set_position_cartesian_pub = rospy.Publisher(self.__full_ros_namespace + '/set_position_cartesian', Pose, latch = True, queue_size = 1)
The issue is that the publisher set_position_cartesian is not publishing the values of the newPose to the robot - can anyone figure out what the issue might be? I can confirm that the def lagrange correctly calculates the values of the x and y coordinates, which are printed out to the terminal via the command rospy.loginfo(newPose). Any help would be greatly appreciated as I've been trying to solve this issue for the last 2 days!
#! /usr/bin/python
import rospy
import sys
from std_msgs.msg import String, Bool, Float32
from geometry_msgs.msg import Pose
from geometry_msgs.msg import PoseStamped
from geometry_msgs.msg import Vector3
from geometry_msgs.msg import Quaternion
from geometry_msgs.msg import Wrench
class example_application:
def callback(self, data):
self.position_cartesian_current = data.pose
rospy.loginfo(data.pose)
def configure(self,robot_name):
self._robot_name = 'PSM1'
ros_namespace = '/dvrk/PSM1'
rospy.Subscriber('/dvrk/PSM1/position_cartesian_current', PoseStamped, self.callback)
self.set_position_cartesian = rospy.Publisher('/dvrk/PSM1/set_position_cartesian', Pose, latch=True, queue_size = 10)
rospy.sleep(3)
rospy.init_node('listener', anonymous=True)
rospy.spin()
def lagrange(self, f, x):
total = 0
n = len(f)
for i in range(n):
xi, yi = f[i]
def g(i, n):
g_tot = 1
for j in range(n):
if i == j:
continue
xj, yj = f[j]
g_tot *= (x - xj) / float(xi - xj)
return g_tot
total += yi * g(i, n)
return total
def trajectoryMover(self):
newPose = Pose()
points =[(0.0156561,0.123151),(0.00715134,0.0035123151),(0.001515177,0.002123151),(0.0071239751,0.09123150)]
xlist = [i*0.001 for i in range(10)]
ylist = [self.lagrange(points, xlist[i])*0.001 for i in range(10)]
for x, y in zip(xlist, ylist):
newPose.position.x = x
newPose.position.y = y
newPose.position.z = 0.001
newPose.orientation.x = 0.001
newPose.orientation.y = 0.001
newPose.orientation.z = 0.005
newPose.orientation.w = 0.002
rospy.sleep(1)
self.set_position_cartesian.publish(newPose)
rospy.loginfo(newPose)
rospy.spin()
def run(self):
# self.home()
self.trajectoryMover()
if __name__ == '__main__':
try:
if (len(sys.argv) != 2):
print(sys.argv[0] + ' requires one argument, i.e. name of dVRK arm')
else:
application = example_application()
application.configure(sys.argv[1])
application.run()
except rospy.ROSInterruptException:
pass
You are not publishing because the code stops at rospy.spin() when you call application.configure(). For what I understand of what you are trying to do, the code will publish 10 poses to a topic, then you don't need it anymore.
I've moved the location of rospy.spin(), but the code needs more revision than that.
#! /usr/bin/python
import rospy
import sys
from std_msgs.msg import String, Bool, Float32
from geometry_msgs.msg import Pose
from geometry_msgs.msg import PoseStamped
from geometry_msgs.msg import Vector3
from geometry_msgs.msg import Quaternion
from geometry_msgs.msg import Wrench
class example_application(object):
def callback(self, data):
self.position_cartesian_current = data.pose
rospy.loginfo(data.pose)
def configure(self,robot_name):
self._robot_name = 'PSM1'
ros_namespace = '/dvrk/PSM1'
rospy.Subscriber('/dvrk/PSM1/position_cartesian_current', PoseStamped, self.callback)
self.set_position_cartesian = rospy.Publisher('/dvrk/PSM1/set_position_cartesian', Pose, latch=True, queue_size = 10)
def lagrange(self, f, x):
total = 0
n = len(f)
for i in range(n):
xi, yi = f[i]
def g(i, n):
g_tot = 1
for j in range(n):
if i == j:
continue
xj, yj = f[j]
g_tot *= (x - xj) / float(xi - xj)
return g_tot
total += yi * g(i, n)
return total
def trajectoryMover(self):
newPose = Pose()
points =[(0.0156561,0.123151),(0.00715134,0.0035123151),(0.001515177,0.002123151),(0.0071239751,0.09123150)]
xlist = [i*0.001 for i in range(10)]
ylist = [self.lagrange(points, xlist[i])*0.001 for i in range(10)]
for x, y in zip(xlist, ylist):
newPose.position.x = x
newPose.position.y = y
newPose.position.z = 0.001
newPose.orientation.x = 0.001
newPose.orientation.y = 0.001
newPose.orientation.z = 0.005
newPose.orientation.w = 0.002
self.set_position_cartesian.publish(newPose)
rospy.loginfo(newPose)
def run(self):
# self.home()
self.trajectoryMover()
if __name__ == '__main__':
if (len(sys.argv) != 2):
print(sys.argv[0] + ' requires one argument, i.e. name of dVRK arm')
else:
application = example_application()
application.configure(sys.argv[1])
application.run()
try:
rospy.spin()
except KeyboardInterrupt:
rospy.loginfo("Keyboard Interrupt")
Think of:
making the script argument a parameter of the node.
moving the configure method to the __init__ method.
taking the g() function outside lagrange().
It's a good practice to use relative topic names, instead of absolute ones (absolute: topic name start with /, e.g.: '/dvrk/PSM1').
Related
Now, in addition from my two previous posts ODE implements I try to refactro my code and fix some problems. And I decided, that logically create such classes: Solver,Problem.
So code for ODE_Solver and FE classes finally code and working.
# ODS.py
import numpy as np
class ODE_Solver(object):
def __init__(self, f):
if not callable(f):
raise TypeError('f is not %s, function' % type(f))
self.f = lambda u, x: np.asarray(f(u, x), float)
self.err_sch = None
def solver_st(self):
raise NotImplementedError
def err_st(self):
raise NotImplementedError
def set_initial_condition(self, u0):
if isinstance(u0, (float, int)):
self.neq = 1
u0 = float(u0)
else:
u0 = np.asarray(u0)
self.neq = u0.size
self.u0 = u0
try:
f0 = self.f(self.u0, 0)
except IndexError:
raise IndexError(
'index out of bounds f(u,x). correct index %s' % (str(range(self.neq))))
if f0.size != self.neq:
raise ValueError('f(u,x) returend %d elems, vector u has %d elems' % (f0.size, self.neq))
def solve(self, coord_points, terminate=None):
if terminate is None:
terminate = lambda u, x, step_no: False
if isinstance(coord_points, (float, int)):
raise TypeError('solve: x points not numpy array or numbers.')
self.x = np.asarray(coord_points)
if self.x.size <= 1:
raise ValueError('ODESolver.solve points of coords less than two')
n = self.x.size
if self.neq == 1: # ОДУ
self.u = np.zeros(n)
self.err_sch = np.zeros(n)
else:
self.u = np.zeros((n, self.neq))
self.err_sch = np.zeros((n, self.neq))
self.u[0] = self.u0
self.err_sch[0] = 0
for k in range(n - 1):
self.k = k
self.u[k + 1] = self.solver_st()
self.err_sch[k + 1] = self.err_st()
if terminate(self.u, self.x, self.k + 1):
break
return self.u[:k + 2], self.x[:k + 2]
# ES.py
from ODS import ODE_Solver
import numpy as np
class FE(ODE_Solver):
def solver_st(self):
u, f, k, x = self.u, self.f, self.k, self.x
dx = x[k + 1] - x[k]
u_new = u[k] + dx * f(u[k], x[k])
return u_new
def err_st(self):
u, f, k, x, err_sch = self.u, self.f, self.k, self.x, self.err_sch
dx = x[k + 1] - x[k]
err_sch = np.max(dx)**2
return err_sch
I try to implement class Problem (return ODE and get initial conditions)
import numpy as np
class Problem(object):
def __init__(self, u0, End):
self.u0 = np.asarray(u0)
self.End = End # end point of coords
def __call__(self, u, x):
return (u[1], u[2], u[3], u[4],
- 15 * u[4] - 90 * u[3] - 270 * u[2] - 405 * u[1] - 243 * u[0])
And code class Solver for call numerical scheme, plotting the final result, plot and evaluate error:
import numpy as np
import matplotlib as plt
import ES
import ODS
from ADS import ABM4
from ES import FE
from MLNS import MLN
from RKS import RK4
class Solver(object):
def __init__(self, problem, dx,
method=ES.FE): # choose FE scheme for tetsting
"""
"""
self.problem, self.dx = problem, dx
self.solver = method
#staticmethod
def choose_sch(type):
if type == 1:
method = FE
return method
elif type == 2:
method = RK4
return method
elif type == 3:
method = ABM4
return method
elif type == 4:
method = MLN
return method
else:
raise ValueError('not choose numerical scheme!')
def dsolve(self):
solver = self.method(self.problem)
solver.set_initial_condition(self.problem.u0)
n = int(round(self.problem.End / self.dx))
x_points = np.linspace(0, self.problem.End, n + 1)
self.u, self.x = solver.solve(x_points)
if solver.k + 1 == n:
self.plot()
raise ValueError('not converge this scheme,' % self.problem.End)
def plot(self):
plt.plot(self.x, self.u)
plt.show()
Now, when I call this Solver and Problem
import numpy as np
from ODE_Problem import Problem
from SLV_Prob import Solver
def test():
problem = Problem(u0=[0, 3, -9, -8, 0], End=5)
solver = Solver(problem, dx=0.1)
solver.dsolve()
solver.plot()
if __name__ == '__main__':
test()
I get the error:
Traceback (most recent call last):
File "C:\Fin_Proj_ODE\test2.py", line 14, in <module>
test()
File "C:\Fin_Proj_ODE\test2.py", line 9, in test
solver.dsolve()
File "C:\Fin_Proj_ODE\SLV_Prob.py", line 37, in dsolve
solver = self.method(self.problem)
AttributeError: 'Solver' object has no attribute 'method'
And I dont' understand and suppose what reason of this bug...
So, I have 2 questions for implement this Solver:
How to fix this bug?
How to correct rewrite def choose_sch(type):, that I could to call solver and send args type ( and depending on it, a specific numerical scheme will already be started)?
Question One:
Well, as the error states, your Solver class doesn't have an attribute called "method". Your attribute is actually "solver", so instead of calling
self.method(self.problem)
Try
self.solver(self.problem)
Question Two:
If I'm understanding you correctly, you want to know how you can call the choose_sch method from within the solver constructor and take in a type instead of a method directly. For that, simply do this:
class Solver(object):
def __init__(self, problem, dx, solver_type=1): # choose FE scheme for tetsting
"""
"""
self.problem, self.dx = problem, dx
self.solver = self._choose_sch(solver_type)
#staticmethod
def _choose_sch(solver_type):
methods = {1: FE, 2: RK4, 3: ABM4, 4: MLN}
if solver_type in methods:
return methods[solver_type]
else:
raise ValueError('not choose numerical scheme!')
The dictionary here is much better than the if statement for these kinds of tasks.
You can also alternatively not make _choose_ach a staticmethod if you don't need to call it from a static context and just make it set the solver directly.
I've implemented a simple direct Nbody simulation in python. I'm looking to parallelize it as we are doing the same operation again and again. In C++, I would have use openmp, but python doesn't have it.
So I was thinking to use the multiprocessing module. From what I understand, I would need a manager to manage the class (and the list particles?) and I was thinking of using a starmap pool.
I'm quite lost on how to use these function to achieve any semblance of parallelization, so any help is appreciated.
PS: I'm open to use other module too, the easier the better. The class is ditchable if using numpy array (for position velocity mass) solves the problem, I'll go with it.
Code:
import numpy as np
import matplotlib.pyplot as plt
import multiprocessing as mp
class particle:
def __init__(self, xy, uv, m):
self.xy =xy # position
self.uv = uv # velocity
self.m = m # mass
self.force = np.zeros([2]) # at t=0s, force =0
def update(self,dt):
self.uv += self.force/self.m * dt
self.xy += self.uv*dt
self.force=np.zeros([2])
def interaction(self,p,jj,eps):
dr = p[jj].xy - self.xy
dr_norm = np.linalg.norm(dr + eps)
self.force += G*self.m*p[jj].m/(dr_norm**2) * dr/dr_norm
p[jj].force -= G*self.m*p[jj].m/(dr_norm**2) * dr/dr_norm
def init_world(n_part):
p=[]
for ii in range(n_part):
p.append(particle(np.random.uniform(0,50,size=(2))*1e15,np.random.uniform(-10,10,size=(2))*0,np.random.uniform(2,25)*1e28))
return p
G = 6.67e-11 # in SI units
dt= 1e5 # in second, 86400s = one day
niter = 10000
n_part = 300
eps = 1e8 #softening to avoid infinite force a small distance
p = init_world(n_part)
xy = np.asarray([p[ii].xy for ii in range(n_part)])
fig, ax1 = plt.subplots()
im = ax1.scatter(xy[:,0],xy[:,1])
plt.show()
for tt in range(niter):
for ii in range(n_part):
for jj in range(ii+1,n_part):
p[ii].interaction(p,jj,eps)
for ii in range(n_part):
p[ii].update(dt)
xy = np.asarray([p[ii].xy for ii in range(n_part)])
ax1.set_title(tt)
im.set_offsets(xy)
plt.pause(0.01)
If you want to share a list of custom objects (such as particle in the question) among processes, you can consider a simplified example here:
import multiprocessing
from multiprocessing.managers import BaseManager
TOTAL_PROCESS = 3
class Particle():
def __init__(self, x, y):
self.x = x
self.y = y
def multiply(self, z):
self.x *= z
self.y *= z
def __repr__(self):
return f'(x={self.x},y={self.y})'
def worker(sharedList, ix):
# Call multiply() for the specific item in the list
sharedList[ix].multiply(2)
def main():
BaseManager.register('Particle', Particle) # Register your custom class
clsManager = BaseManager() # A manager to manage Particle objects
clsManager.start()
manager = multiprocessing.Manager() # Another manager to manage a shared list
# Create a list of Particle objects
sharedList = manager.list([clsManager.Particle(x, x+1) for x in range(0, TOTAL_PROCESS)])
# See the origina list
for x in sharedList:
print(x, end=' ')
else:
print()
# Run multiple processes and to make each of them them to work on a specific object only
processes = []
for i in range(TOTAL_PROCESS):
p = multiprocessing.Process(target=worker, args=[sharedList, i])
p.start()
processes.append(p)
for p in processes:
p.join()
# See the updated list of Pariticle objects
for x in sharedList:
print(x, end=' ')
else:
print()
if __name__ == '__main__':
main()
import random
class point:
def __init__(self,p):
self.p = p
def fill_point(self):
x = random.uniform(0,100)
y = random.uniform(0,100)
z = random.uniform(0,100)
self.p = [x,y,z]
return self.p
def distance_between_points(self,p1,p2):
D = ((self.p1[0]-self.p2[0])**2 + (self.p1[1]-self.p2[1])**2 + (self.p1[2]-self.p2[2])**2)**(1/2)
return D
def main():
point1 = point(fill_point())
point2 = point(fill_point())
Distance = distance_between_points(point1,point2)
print(Distance)
main()
im quite new to classes and am having a hard time understanding what im doing wrong.
import random
from math import sqrt
class Point:
def __init__(self, name='anonym_point',x=0,y=0,z=0):
self.name = name
self.x = x
self.y = y
self.z = z
#property
def coord(self):
return (self.x, self.y, self.z)
def __repr__(self):
return ("{} has coordinate {} {} {}".format(self.name, self.x, self.y, self.z))
def makepoint(namepoint):
return Point(namepoint, random.uniform(0,100), random.uniform(0,100), random.uniform(0,100))
def distance_between_points(p1,p2):
dist = sqrt((p2.x-p1.x)**2 + (p2.y-p1.y)**2 + (p2.z-p1.z)**2)
print("distance between point ",p1.name," and the point ",p2.name," : ",dist)
point1 = makepoint("p1")
point2 = makepoint("p2")
print(point1)
print(point2)
Distance = distance_between_points(point1,point2)
The issue is that you are accessing the class method just as a normal method, you need to initialize class object first then call the method by class object you created, again use only the variables you are sure you need,. keeping code easy for you because I think you already know what you needed I did this
import random
class Point:
def fill_point(self):
x = random.uniform(0,100)
y = random.uniform(0,100)
z = random.uniform(0,100)
p = [x,y,z]
return p
def distance_between_points(self,p1,p2):
D = ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2 + (p1[2]-p2[2])**2)**(1/2)
return D
def main():
obj = Point()
point1 = obj.fill_point()
point2 = obj.fill_point()
distance = obj.distance_between_points(point1,point2)
print(distance)
main()
it would not kill if you try to understand python classes better, python best naming, etc...
i think that what you are trying to do is something like this.
import math
class Point():
def __init__(self,x,y,z):
self.coordinates = (x,y,z)
def distance(self,point):
return math.sqrt((point.coordinates[0] - self.coordinates[0])**2 + (point.coordinates[1] - self.coordinates[1])**2 + (point.coordinates[1] - self.coordinates[1])**2)
a = Point(4,2,8)
b = Point(2,7,3)
print(a.distance(b))
what you are doing by executing this python code is simply creating a "Point" class, this point class has an attribute (coordinates) that contains its coordinates into a tuple.
so to create a point object you just have to use this code point = Point(x_coordinates,y_coordinates,z_coordinates).
In the last line the code calculates the distance between two points, in that case you are calculating the distance of "b" respect to "a", but you could also do viceversa by doing something like this: print(b.distance(a))
to calculate the distance between random point all you have to do is this:
import math, random
class Point():
def __init__(self,x,y,z):
self.coordinates = (x,y,z)
def distance(self,point):
return math.sqrt((point.coordinates[0] - self.coordinates[0])**2 + (point.coordinates[1] - self.coordinates[1])**2 + (point.coordinates[1] - self.coordinates[1])**2)
r = lambda: random.uniform(0,100)
a = Point(r(),r(),r())
b = Point(r(),r(),r())
print(a.distance(b))
this is how to do that, but i really don't understand why you should calculate the distance between two random numbers
The below code simulates a stock price and calculates its payoff. I am trying to use multiprocessing to speed up the simulations. The problem is that in CallUpAndOut where I have pool.map, I am not sure how to access total from simulations
I have tried several things like self.Simulations.Total or self.total but it doesn't work.
import numpy as np
from multiprocessing import Pool
import time
class PricingSimulatedBarrierOption:
def __init__(self, spot, strike, barrier, rate, sigma, time, sims, steps):
self.spot = spot
self.strike = strike
self.barrier = barrier
self.rate = rate
self.sigma = sigma
self.time = time
self.sims = sims
self.steps = steps
self.dt = self.time / self.steps
def Simulations(self):
total = np.zeros((self.sims,self.steps+1),float)
pathwiseS= np.zeros((self.steps+1),float)
for j in range(self.sims):
pathwiseS[0] =self.spot
total[j,0] = self.spot
for i in range(1,self.steps+1):
phi = np.random.normal()
pathwiseS[i] = pathwiseS[i-1]*(1+self.rate*self.dt+self.sigma*phi*np.sqrt(self.dt))
total[j,i]= pathwiseS[i]
return total.reshape(self.sims, self.steps+1)
def CallUpAndOut(self):
start_time = time.time()
p = Pool()
getpayoff = p.map(self.Simulations(),self.total) ###How to pass total here?
p.close()
p.join()
end_time = time.time()-start_time
print(end_time)
# getpayoff = self.Simulations()
callpayoff = np.zeros((self.sims),float)
for j in range(self.sims):
if max(getpayoff[j,])>=self.barrier:
callpayoff[j] = 0
else:
callpayoff[j] = max(getpayoff[j,self.steps-1]-self.strike,0)
return np.exp(-self.rate*self.time)*np.average(callpayoff)
c = PricingSimulatedBarrierOption(100,100,170,0.05,0.2,1,10000,252)
print(c.CallUpAndOut())
In function definition add parameter see below example:
def CallUpAndOut(self, total):
And pass array of total values in map see below example:
total = [1,2,3]
getpayoff = p.map(self.Simulations,total)
To work this I had to move the declaration outside. Below code is now able to accept variable in the Pool function.
import numpy as np
from multiprocessing import Pool
import time
class PricingSimulatedBarrierOption:
def __init__(self, spot, strike, barrier, rate, sigma, time, sims, steps):
self.spot = spot
self.strike = strike
self.barrier = barrier
self.rate = rate
self.sigma = sigma
self.time = time
self.sims = sims
self.steps = steps
self.dt = self.time / self.steps
self.pathwiseS= np.zeros((self.steps+1),float)
def Simulations(self):
print("Called")
total = np.zeros((self.sims,self.steps+1),float)
self.pathwiseS= np.zeros((self.steps+1),float)
for j in range(self.sims):
self.pathwiseS[0] =self.spot
total[j,0] = self.spot
for i in range(1,self.steps+1):
phi = np.random.normal()
self.pathwiseS[i] = self.pathwiseS[i-1]*(1+self.rate*self.dt+self.sigma*phi*np.sqrt(self.dt))
total[j,i]= self.pathwiseS[i]
return total.reshape(self.sims, self.steps+1)
def CallUpAndOut(self):
start_time = time.time()
p = Pool()
getpayoff = p.map(self.Simulations(),self.pathwiseS)
p.close()
p.join()
end_time = time.time()-start_time
print(end_time)
# getpayoff = self.Simulations()
callpayoff = np.zeros((self.sims),float)
for j in range(self.sims):
if max(getpayoff[j,])>=self.barrier:
callpayoff[j] = 0
else:
callpayoff[j] = max(getpayoff[j,self.steps-1]-self.strike,0)
return np.exp(-self.rate*self.time)*np.average(callpayoff)
I need to both calculate and plot the integral below in Python:
integral of the function e^(-t^2) from x=0 to x=3
So far I've managed to calculate the integral using Simpson's rule. The next bit which I'm struggling with is plotting the integral of e^(-t^2) vs x from x=0 to x=3 (see the image above).
Here's the code I've written to calculate the integral -
from math import exp
def f(t):
return exp(-(t**2))
a = 0
b = 3
h = 0.1
N = int((b-a)/h)
s_even = 0
s_odd = 0
for k in range(1,N,2):
s_odd += f(a+k*h)
for k in range(2,N,2):
s_even += f(a+k*h)
s = f(a) + f(b) + 4*s_odd + 2*s_even
Integral = h*s/3
print(Integral)
How do I then create a graph of this integral?
Here's a script I wrote that performs your calculation and plots it using PyQtGraph:
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
from math import exp
class I:
def f(self,t):
return exp(-(t**2))
def __init__(self, a = 0, b = 3, h = 0.1):
N = int((b-a)/h)
s_even = s_odd = 0
for k in range(1,N,2):
s_odd += self.f(a+k*h)
for k in range(2,N,2):
s_even += self.f(a+k*h)
s = self.f(a) + self.f(b) + 4*s_odd + 2*s_even
self.I = h*s/3
def __str__(self):
return "I: %s" % self.I
def plot(array):
app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Basic plotting examples")
win.resize(1000,600)
win.setWindowTitle('pyqtgraph example: Plotting')
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
p1 = win.addPlot(title="Basic array plotting", y=array)
QtGui.QApplication.instance().exec_()
def main():
a=0
b=a+0.001
points=[]
while(a<3):
points.append(I(a,b).I)
a=b
b=a+0.001
plot(points)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
main()
Here is the graph it draws:
Thanks for your help Red Cricket. It looks like you may have graphed the function e^(-t^2) rather than the integral of that function. Nonetheless, I think I've worked it out; I've discovered scipy has an integrate function:
from math import exp
from numpy import arange
from scipy import integrate
def f(t):
return exp(-(t**2))
a = 0
b = 3
h = 0.1
N = int((b-a)/h)
s_even = 0
s_odd = 0
for k in range(1,N,2):
s_odd += f(a+k*h)
for k in range(2,N,2):
s_even += f(a+k*h)
s = f(a) + f(b) + 4*s_odd + 2*s_even
I = h*s/3
function = []
x = []
for t in arange(0,4,h):
function.append(f(t))
for i in arange(0,4,h):
x.append(i)
function_int = integrate.cumtrapz(function,x,initial=0)
plot(x,function_int)
show()
print(I)
This produces a graph of the integral and prints the final value of the integral itself. Hooray!