Add two vectors using python's operator overloading feauture - python-3.x

I want to add two vectors with n dimensions using the add method operator overloading . The elements of the 2 vectors will be input by the user. I don't understand how to define the vector as a single object.
In my example code vectors s1 and s2 have 2 defined values.I want the vectors to take input from the user having N dimensions and then add them using class and the add method.I can do it using only functions without using class and object but it is for a homework and the use of class is required.For example :
s1 = [float(x) for x in input().split()]
s2= [float(x) for x in input().split()]
s3=s1+s2
I am clueless on what to do and any help will be appreciated.
class Student :
def __init__(self,m1,m2) :
self.m1=m1
self.m2=m2
def __add__(self,other) :
m1=self.m1+other.m1
m2=self.m2+other.m2
s3=Student(m1,m2)
return s3
s1=Student(58,69)
s2=Student(60,65)
s3=s1+s2
print(s3.m1,s3.m2)

If you are allowed to use numpy following solution will work:
import numpy as np
x = np.array([float(x) for x in "1 2 3".split()])
y = np.array([float(x) for x in "3 4 5".split()])
print(x)
# [1. 2. 3.]
print(y)
# [3. 4. 5.]
print(x+y)
# [4. 6. 8.]
class Student:
def __init__(self, data):
self.data = data
def __add__(self, other):
return Student(self.data+other.data)
student_x = Student(x)
student_y = Student(y)
print((student_x+student_y).data)
# [4. 6. 8.]

Related

How to make a copy of an object each time I assign it to a new variable?

To keep it simple, I wanna do a new type that emulate a coordinate in 2D space.
class Vector2:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __str__(self): # for print()
return f"({self.x}, {sefl.y})"
But I went into a problem, when I assign a vector to another variable, any change to one of them will affect both.
>>> A = Vector2(1.0, 2.0)
>>> B = A # my problem is that
>>> A.x = 2.0 # changing the x attribute of A
>>> print(B) # affect B and I don't want that
(2.0, 2.0)
I know some special methods (__set__ or __get__) exist which can make that when writing this B = A, B will be a copy of A instead of A himself.
Now, what special method should I use and how should I use it?
I tried to find an anwser there but I didn't found.
specification: I'm using python3.9.5 on linux

List values lost in Python Multiprocessing

I would like to parallelise a series of tasks operating on a list of NetworkX graphs. For parallelisation I use the Manager and Process objects from the Multiprocessing module. In the minimal example below there is only one process calculating the adjacency matrix of each NetworkX graph. The set of graphs is stored in a list Gt. Each particular graph from this set is called Gk. Similarly, the adjacency matrices are stored in a list called At, while each particular matrix corresponding to a graph Gk is called Ak. I use keyword arguments to pass the global list of adjacency matrices At and an index k to a function adj_mtrx(). My problem is that I cannot obtain the calculated adjacency values in the main body of the program. At[k] are all zeroes. If possible, would you please take a look at the minimal example below and direct me to my mistake?
#!/usr/bin/env python3
import networkx as nx
import numpy as np
import random as rnd
from multiprocessing import Manager, Process
# Generates random graph
def gen_rnd_graph(nv, ne):
# Create random list of sources
Vsrc = [rnd.randint(0,nv-1) for iter in range(ne)]
# Create random list of sinks
Vsnk = [rnd.randint(0,nv-1) for iter in range(ne)]
# Create random list of edge weights
U = [rnd.random() for iter in range(ne)]
# Create list of tuples {Vsrc, Vsnk, U}
T = list(zip(Vsrc,Vsnk,U))
# Create graph
G = nx.Graph()
# Create list of vertices
V = list(range(nv))
# Add nodes to graph
G.add_nodes_from(V)
# Add edges between random vertices with random edge weights
G.add_weighted_edges_from(T)
return G
# Generates random time-varying graph
def gen_time_graph(nv, ne, ng):
# Initialise list of graphs
l = []
for i in range(ng):
gi = gen_rnd_graph(nv, ne)
l.append(gi)
return l
# Computes adjacency matrix for snaphot of time-varying graph
def adj_mtrx(Gk, **kwargs):
At = kwargs.get("At", None)
k = kwargs.get("k", None)
print("in adj_mtrx id(At):", id(At))
# no. of vertices
n = Gk.number_of_nodes()
# adjacency matrix
Ak = np.zeros([n,n])
# for each vertex
for i in range(n):
for j in range(n):
if Gk.has_edge(i,j):
Ak[i,j] = 1
print("func Ak[{0:d},{1:d}]: {2:f}".format(i, j, Ak[i,j]))
# Store new At[k] values
if At != None and k != None:
At[k][i,j] = Ak[i,j]
if At[k][i,j] > 0.0:
print("func At[{0:d}][{1:d},{2:d}]: {3:f}".format(k, i, j, At[k][i,j]))
return Ak
def main():
with Manager() as manager:
#-----------------------------------------------------------------------
# Specify constants
#-----------------------------------------------------------------------
NV = 10 # no. of vertices
NE = 15 # no. of edges
NG = 3 # no. of snapshot graphs
#-----------------------------------------------------------------------
# Generate random time-varying graph
#-----------------------------------------------------------------------
Gt = manager.list()
Gt = gen_time_graph(NV, NE, NG)
# Snapshot index
k = 0
# Initialise list of temporal adjacency matrics
At = manager.list()
At =[np.zeros([NV,NV])]*NG
print("create id(At):", id(At))
# for each snapshot graph
for Gk in Gt:
print("k: {0:d}".format(k))
processes = []
# Temporal adjacency matrix
print("pre adj_mtrx id(At):", id(At))
p1 = Process( target=adj_mtrx, args=(Gk,), kwargs={"At": At, "k": k} )
print("post adj_mtrx id(At):", id(At))
p1.start()
processes.append(p1)
# Wait for process 1
p1.join()
# #test
[m,n] = np.shape(At[k])
for i in range(m):
for j in range(n):
if At[k][i,j] > 0.0:
print("body At[{0:d}][{1:d},{2:d}]: {3:f}".format(k, i, j, At[k][i,j]))
k += 1
if __name__ == '__main__':
main()
multiprocessing supports two types of communication channel between processes: multiprocessing.Queue and multiprocessing.Pipe.
In your function 'adj_mtrx' you are modifying 'At' variable, which in your case is a deep copy of the 'At' variable you are passing to Process(…) in the 'main' function. All modifications to the 'At' variable will be local to the 'adj_mtrx'.
You can use the Multiprocessing.Pool class.
Instantiate a pool with the number of processes you want to run in parallel and use its map function to iterate over the list of graphs and calculate the adj matrix for each graph using your function.
In fact networkx provides it's own function for calculating adj matrix (nx.to_numpy_array)
see the code below :
import networkx as nx
import numpy as np
import random as rnd
import multiprocessing
from multiprocessing import Pool
# Generates random graph
def gen_rnd_graph(nv, ne):
G = nx.gnm_random_graph(nv, ne, seed=None, directed=False)
for s, t in G.edges():
G[s][t]['weight'] = rnd.random()
return G
# Generates random time-varying graph
def gen_time_graph(nv, ne, ng):
# Initialise list of graphs
l = []
for i in range(ng):
gi = gen_rnd_graph(nv, ne)
l.append(gi)
return l
# Computes adjacency matrix for snaphot of time-varying graph
def adj_mtrx(Gk):
Ak = nx.to_numpy_array(Gk, weight=1) # weight parameter make sure adj is 1 instead of actual weight
return Ak
def main():
num_of_processes = multiprocessing.cpu_count() // 2
print('num_of_process={}'.format(num_of_processes))
# -----------------------------------------------------------------------
# Specify constants
# -----------------------------------------------------------------------
NV = 10 # no. of vertices
NE = 15 # no. of edges
NG = 3 # no. of snapshot graphs
# -----------------------------------------------------------------------
# Generate random time-varying graph
# -----------------------------------------------------------------------
Gt = gen_time_graph(NV, NE, NG)
with Pool(num_of_processes) as p:
At = p.map(adj_mtrx, Gt)
for k in range(NG):
print(Gt[k].edges())
print(At[k])
print('==========')
if __name__ == '__main__':
main()
I've also used networkx random graph generation method to make the code more compact, it differs slightly from your implementation as it wont have self edges and it guarantees that NE edges will be produced (no duplicate edges)

Python3, scipy.optimize: Fit model to multiple datas sets

I have a model which is defined as:
m(x,z) = C1*x^2*sin(z)+C2*x^3*cos(z)
I have multiple data sets for different z (z=1, z=2, z=3), in which they give me m(x,z) as a function of x.
The parameters C1 and C2 have to be the same for all z values.
So I have to fit my model to the three data sets simultaneously otherwise I will have different values of C1 and C2 for different values of z.
It this possible to do with scipy.optimize.
I can do it for just one value of z, but can't figure out how to do it for all z's.
For one z I just write this:
def my_function(x,C1,C1):
z=1
return C1*x**2*np.sin(z)+ C2*x**3*np.cos(z)
data = 'some/path/for/data/z=1'
x= data[:,0]
y= data[:,1]
from lmfit import Model
gmodel = Model(my_function)
result = gmodel.fit(y, x=x, C1=1.1)
print(result.fit_report())
How can I do it for multiple set of datas (i.e different z values?)
So what you want to do is fit a multi-dimensional fit (2-D in your case) to your data; that way for the entire data set you get a single set of C parameters that bests describes your data. I think the best way to do this is using scipy.optimize.curve_fit().
So your code would look something like this:
import scipy.optimize as optimize
import numpy as np
def my_function(xz, *par):
""" Here xz is a 2D array, so in the form [x, z] using your variables, and *par is an array of arguments (C1, C2) in your case """
x = xz[:,0]
z = xz[:,1]
return par[0] * x**2 * np.sin(z) + par[1] * x**3 * np.cos(z)
# generate fake data. You will presumable have this already
x = np.linspace(0, 10, 100)
z = np.linspace(0, 3, 100)
xx, zz = np.meshgrid(x, z)
xz = np.array([xx.flatten(), zz.flatten()]).T
fakeDataCoefficients = [4, 6.5]
fakeData = my_function(xz, *fakeDataCoefficients) + np.random.uniform(-0.5, 0.5, xx.size)
# Fit the fake data and return the set of coefficients that jointly fit the x and z
# points (and will hopefully be the same as the fakeDataCoefficients
popt, _ = optimize.curve_fit(my_function, xz, fakeData, p0=fakeDataCoefficients)
# Print the results
print(popt)
When I do this fit I get precisely the fakeDataCoefficients I used to generate the function, so the fit works well.
So the conclusion is that you don't do 3 fits independently, setting the value of z each time, but instead you do a 2D fit which takes the values of x and z simultaneously to find the best coefficients.
Your code is incomplete and has a few syntax errors.
But I think that you want to build a model that concatenates the models for the different data sets, and then fit the concatenated data to that model. Within the context of lmfit (disclosure: author and maintainer), I often find it easier to use minimize() and an objective function for multiple data set fits rather than the Model class. Perhaps start with something like this:
import lmfit
import numpy as np
# define the model function for each dataset
def my_function(x, c1, c2, z=1):
return C1*x**2*np.sin(z)+ C2*x**3*np.cos(z)
# Then write an objective function like this
def f2min(params, x, data2d, zlist):
ndata, npts = data2d.shape
residual = 0.0*data2d[:]
for i in range(ndata):
c1 = params['c1_%d' % (i+1)].value
c2 = params['c2_%d' % (i+1)].value
residual[i,:] = data[i,:] - my_function(x, c1, c2, z=zlist[i])
return residual.flatten()
# now build that `data2d`, `zlist` and build the `Parameters`
data2d = []
zlist = []
x = None
for fname in dataset_names:
d = np.loadtxt(fname) # or however you read / generate data
if x is None: x = d[:, 0]
data2d.append(d[:, 1])
zlist.append(z_for_dataset(fname)) # or however ...
data2d = np.array(data2d) # turn list into nd array
ndata, npts = data2d.shape
params = lmfit.Parameters()
for i in range(ndata):
params.add('c1_%d' % (i+1), value=1.0) # give a better starting value!
params.add('c2_%d' % (i+1), value=1.0) # give a better starting value!
# now you're ready to do the fit and print out the results:
result = lmfit.minimize(f2min, params, args=(x, data2d, zlist))
print(results.fit_report())
That code really a sketch and is all untested, but hopefully will give you a good starting foundation.

Numpy: division by zero error, but mathematically function is apparently defined

I'm testing out some functions to fit with data, and one of them (in 2-D) is
f(x) = (1/(1-x)) / (1 + 1/(1-x))
Which, according to Wolfram and the Google plotters, gives you the result
f(1) = 1
I've tried to get this to work without hard coding the case
if x == 1:
return 1
but I end up with a nan and a RunTimeWarning informing me that I have indeed divided by zero.
import numpy as np
def f(x):
return 1/(1-x) / (1 + 1/(1-x))
x_range = np.linspace(0, 1, 50)
y = f(x_range)
print(y)
Is there a more elegant solution than to simply introduce a hard-coded if?
Is there a reason to keep it in this form, you can simplify it to:
def f(x):
return 1/(2-x)
Wolfram and Google probably to some sort of algebraic simplification too.
Just simplify the equation for f(x) = (1/(1-x)) / (1 + 1/(1-x)). The simplified equation will be (1/(2-x)). Now update the program as:
import numpy as np
def f(x):
return 1/(2-x)
x_range = np.linspace(0, 1, 50)
y = f(x_range)
print(y)
output:
[0.5 0.50515464 0.51041667 0.51578947 0.5212766 0.52688172
0.5326087 0.53846154 0.54444444 0.5505618 0.55681818 0.56321839
0.56976744 0.57647059 0.58333333 0.59036145 0.59756098 0.60493827
0.6125 0.62025316 0.62820513 0.63636364 0.64473684 0.65333333
0.66216216 0.67123288 0.68055556 0.69014085 0.7 0.71014493
0.72058824 0.73134328 0.74242424 0.75384615 0.765625 0.77777778
0.79032258 0.80327869 0.81666667 0.83050847 0.84482759 0.85964912
0.875 0.89090909 0.90740741 0.9245283 0.94230769 0.96078431
0.98 1. ]

Python/Pandas element wise union of 2 Series containing sets in each element

I have 2 pandas data Series that I know are the same length. Each Series contains sets() in each element. I want to figure out a computationally efficient way to get the element wise union of these two Series' sets. I've created a simplified version of the code with fake and short Series to play with below. This implementation is a VERY inefficient way of doing this. There has GOT to be a faster way to do this. My real Series are much longer and I have to do this operation hundreds of thousands of times.
import pandas as pd
set_series_1 = pd.Series([{1,2,3}, {'a','b'}, {2.3, 5.4}])
set_series_2 = pd.Series([{2,4,7}, {'a','f','g'}, {0.0, 15.6}])
n = set_series_1.shape[0]
for i in range(0,n):
set_series_1[i] = set_series_1[i].union(set_series_2[i])
print set_series_1
>>> set_series_1
0 set([1, 2, 3, 4, 7])
1 set([a, b, g, f])
2 set([0.0, 2.3, 15.6, 5.4])
dtype: object
I've tried combining the Series into a data frame and using the apply function, but I get an error saying that sets are not supported as dataframe elements.
pir4
After testing several options, I finally came up with a good one... pir4 below.
Testing
def jed1(s1, s2):
s = s1.copy()
n = s1.shape[0]
for i in range(n):
s[i] = s2[i].union(s1[i])
return s
def pir1(s1, s2):
return pd.Series([item.union(s2[i]) for i, item in enumerate(s1.values)], s1.index)
def pir2(s1, s2):
return pd.Series([item.union(s2[i]) for i, item in s1.iteritems()], s1.index)
def pir3(s1, s2):
return s1.apply(list).add(s2.apply(list)).apply(set)
def pir4(s1, s2):
return pd.Series([set.union(*z) for z in zip(s1, s2)])

Resources