Related
import numpy as np
from time import time
import matplotlib.pyplot as plt
np.random.seed(27)
mysetup = "from math import sqrt"
begin=time()
i=int(input("Number of rows in first matrix"))
k=int(input("Number of column in first and rows in second matrix"))
j=int(input("Number of columns in second matrix"))
A = np.random.randint(1,10,size = (i,k))
B = np.random.randint(1,10,size = (k,j))
def multiply_matrix(A,B):
global C
if A.shape[1]==B.shape[0]:
C=np.zeros((A.shape[0],B.shape[1]),dtype=int)
for row in range(i):
for col in range(j):
for elt in range(0,len(B)):
C[row,col] += A[row,elt]*B[elt,col]
return C
else:
return "Cannot multiply A and B"
print(f"Matrix A:\n {A}\n")
print(f"Matrix B:\n {B}\n")
D=print(multiply_matrix(A, B))
end=time()
t=print(end-begin)
x=[0,100,10]
y=[100,100,1000]
plt.plot(x,y)
plt.xlabel('Time taken for the program to run')
plt.ylabel('Order of the matrix multiplication')
plt.show()
In the program, I have generated random elements for the matrices to be multiplied.Basically I am trying to compute the time it takes to multiply two matrices.The i,j and k will be considered as the order used for the matrix.As we cannot multiply matrices where number of columns of the first is not equal to the number of the rows in the second, I have already given them the variable 'k'.
Initially I considered to increment the order of the matrix using for loop but wasn't able to do so. I want the graph to display the time it took to multiply the matrices on the x axis and the order of the resultant matrix on the y axis.
There is a problem in the logic I applied but I am not able to find out how to do this problem as I am a beginner in programming
I was expecting to get the result as Y axis having a scale ranging from 0 to 100 with a difference of 10 and x axis with a scale of 100 to 1000 with a difference of 100.
The thousandth entity on the x axis will correspond to the time it took to compute the multiplication of two matrices with numbers of rows and columns as 1000.
Suppose the time it took to compute this was 200seconds. So the graph should be showing the point(1000,200).
Some problematic points I'd like to address -
You're starting the timer before the user chooses an input - which can differ, we want to be as precise as possible, thus we need to only calculate how much time it takes for the multiply_matrix function to run.
Because you're taking an input - it means that each run you will get one result, and one result is only a single point - not a full graph, so we need to get rid of the user input and generate our own.
Moreover to point #2 - we are not interested in giving "one shot" for each matrix order - that means that when we want to test how much time it takes to multiply two matrices of order 300 (for example) - we need to do it N times and take the average in order to be more precise, not to mention we are generating random numbers, and it is possible that some random generated matrices will be easier to compute than other... although taking the average over N tests is not 100% accurate - it does help.
You don't need to set C as a global variable as it can be a local variable of the function multiply_matrix that we anyways return. Also this is not the usage of globals as even with the global C - it will be undefined in the module level.
This is not a must, but it can improve a little bit your program - use time.perf_counter() as it uses the clock with the highest (available) resolution to measure a short duration, and it avoids precision loss by the float type.
You need to change the axes because we want to see how the time is affected by the order of the matrices, not the opposite! (so our X axis is now the order and the Y is the average time it took to multiply them)
Those fixes translate to this code:
Calculating how much it takes for multiply_matrix only.
begin = time.perf_counter()
C = multiply_matrix(A, B)
end = time.perf_counter()
2+3. Generating our own data, looping from order 1 to order maximum_order, taking 50 tests for each order:
maximum_order = 50
tests_number_for_each_order = 50
def generate_matrices_to_graph():
matrix_orders = [] # our X
multiply_average_time = [] # our Y
for order in range(1, maximum_order):
print(order)
times_for_each_order = []
for _ in range(tests_amount_for_each_order):
# generating random square matrices of size order.
A = np.random.randint(1, 10, size=(order, order))
B = np.random.randint(1, 10, size=(order, order))
# getting the time it took to compute
begin = time.perf_counter()
multiply_matrix(A, B)
end = time.perf_counter()
# adding it to the times list
times_for_each_order.append(end - begin)
# adding the data about the order and the average time it took to compute
matrix_orders.append(order)
multiply_average_time.append(sum(times_for_each_order) / tests_amount_for_each_order) # average
return matrix_orders, multiply_average_time
Minor changes to multiply_matrix as we don't need i, j, k from the user:
def multiply_matrix(A, B):
matrix_order = A.shape[1]
C = np.zeros((matrix_order, matrix_order), dtype=int)
for row in range(matrix_order):
for col in range(matrix_order):
for elt in range(0, len(B)):
C[row, col] += A[row, elt] * B[elt, col]
return C
and finally call generate_matrices_to_graph
# calling the generate_data_and_compute function
plt.plot(*generate_matrices_to_graph())
plt.xlabel('Matrix order')
plt.ylabel('Time [in seconds]')
plt.show()
Some outputs:
We can see that when our tests_number_for_each_order is small, the graph loses precision and crisp.
Going from order 1-40 with 1 test for each order:
Going from order 1-40 with 30 tests for each order:
Going from order 1-40 with 80 tests for each order:
I love this kind of questions:
import numpy as np
from time import time
import matplotlib.pyplot as plt
np.random.seed(27)
dim = []
times = []
for i in range(1,10001,10):
A = np.random.randint(1,10,size=(1,i))
B = np.random.randint(1,10,size=(i,1))
begin = time()
C = A*B
times.append(time()-begin)
dim.append(i)
plt.plot(times,dim)
This is a simplified test in which I tested 1 dimension matrices, (1,1)(1,1), (1,10)(10,1), (1,20)(20,1) and so on...
But you can make a double iteration to change also the "outer" dimension of the matrices and see how this affect the computational time
I have a numpy array whose values are distributed in the following manner
From this array I need to get a random sub-sample which is normally distributed.
I need to get rid of the values from the array which are above the red line in the picture. i.e. I need to get rid of some occurences of certain values from the array so that my distribution gets smoothened when the abrupt peaks are removed.
And my array's distribution should become like this:
Can this be achieved in python, without manually looking for entries corresponding to the peaks and remove some occurences of them ? Can this be done in a simpler way ?
The following kind of works, it is rather aggressive, though:
It works by ordering the samples, transforming to uniform and then trying to select a regular griddish subsample. If you feel it is too aggressive you could increase ns which is essentially the number of samples kept.
Also, please note that it requires the knowledge of the true distribution. In case of normal distribution you should be fine with using sample mean and unbiased variance estimate (the one with n-1).
Code (without plotting):
import scipy.stats as ss
import numpy as np
a = ss.norm.rvs(size=1000)
b = ss.uniform.rvs(size=1000)<0.4
a[b] += 0.1*np.sin(10*a[b])
def smooth(a, gran=25):
o = np.argsort(a)
s = ss.norm.cdf(a[o])
ns = int(gran / np.max(s[gran:] - s[:-gran]))
grid, dp = np.linspace(0, 1, ns, endpoint=False, retstep=True)
grid += dp/2
idx = np.searchsorted(s, grid)
c = np.flatnonzero(idx[1:] <= idx[:-1])
while c.size > 0:
idx[c+1] = idx[c] + 1
c = np.flatnonzero(idx[1:] <= idx[:-1])
idx = idx[:np.searchsorted(idx, len(a))]
return o[idx]
ap = a[smooth(a)]
c, b = np.histogram(a, 40)
cp, _ = np.histogram(ap, b)
I have an image that is of size 50000x50000. It has around 25000 connected different connected components. I'm using ndimage.label to label each of them and then I find the non zero points and finally get the min x, max x, min y and max y values. However, I have to find these coordinates is for each of the 25000 connected components. This is expensive as I have to run np.nonzero on the 50000x50000 image 25000 times. Here is a snippet of the code doing what I just mentioned.
im, _ = ndimage.label(im)
num_instances = np.max(np.max(im))
for instance_id in range(1,num_instances+1):
im_inst = im == instance_id
points = np.nonzero(im_inst) # running this is expensive as im is 50000x50000
cropped_min_x_1 = np.min(points[0])
cropped_min_y_1 = np.min(points[1])
cropped_max_x_1 = np.max(points[0])+1
cropped_max_y_1 = np.max(points[1])+1
Does anyone know what I can do to significantly speed up this process?
If the fraction of labelled pixels is not too large:
nz = np.flatnonzero(im)
order = np.argsort(im.ravel()[nz])
nz = nz[order]
blocks = np.searchsorted(im.ravel()[nz], np.arange(2, num_instances+1))
# or (which is faster will depend on numbers)
blocks = 1 + np.where(np.diff(im.ravel()[nz]))[0]
coords = np.array(np.unravel_index(nz, (50000, 50000)))
groups = np.split(coords, blocks, axis=-1)
groups will be a list of 2xn_i coordinates where n_i is the size of component i.
I would like to optimize over all 30 by 30 matrices with entries that are 0 or 1. My objective function is the determinant. One way to do this would be some sort of stochastic gradient descent or simulated annealing.
I looked at scipy.optimize but it doesn't seem to support this sort of optimization as far as I can tell. scipy.optimize.basinhopping looked very tempting but it seems to require continuous variables.
Are there any tools in Python for this sort of general discrete optimization?
I think a genetic algorithm might work quite well in this case. Here's a quick example thrown together using deap, based loosely on their example here:
import numpy as np
import deap
from deap import algorithms, base, tools
import imp
class GeneticDetMinimizer(object):
def __init__(self, N=30, popsize=500):
# we want the creator module to be local to this instance, since
# creator.create() directly adds new classes to the module's globals()
# (yuck!)
cr = imp.load_module('cr', *imp.find_module('creator', deap.__path__))
self._cr = cr
self._cr.create("FitnessMin", base.Fitness, weights=(-1.0,))
self._cr.create("Individual", np.ndarray, fitness=self._cr.FitnessMin)
self._tb = base.Toolbox()
# an 'individual' consists of an (N^2,) flat numpy array of 0s and 1s
self.N = N
self.indiv_size = N * N
self._tb.register("attr_bool", np.random.random_integers, 0, 1)
self._tb.register("individual", tools.initRepeat, self._cr.Individual,
self._tb.attr_bool, n=self.indiv_size)
# the 'population' consists of a list of such individuals
self._tb.register("population", tools.initRepeat, list,
self._tb.individual)
self._tb.register("evaluate", self.fitness)
self._tb.register("mate", self.crossover)
self._tb.register("mutate", tools.mutFlipBit, indpb=0.025)
self._tb.register("select", tools.selTournament, tournsize=3)
# create an initial population, and initialize a hall-of-fame to store
# the best individual
self.pop = self._tb.population(n=popsize)
self.hof = tools.HallOfFame(1, similar=np.array_equal)
# print summary statistics for the population on each iteration
self.stats = tools.Statistics(lambda ind: ind.fitness.values)
self.stats.register("avg", np.mean)
self.stats.register("std", np.std)
self.stats.register("min", np.min)
self.stats.register("max", np.max)
def fitness(self, individual):
"""
assigns a fitness value to each individual, based on the determinant
"""
return np.linalg.det(individual.reshape(self.N, self.N)),
def crossover(self, ind1, ind2):
"""
randomly swaps a subset of array values between two individuals
"""
size = self.indiv_size
cx1 = np.random.random_integers(0, size - 2)
cx2 = np.random.random_integers(cx1, size - 1)
ind1[cx1:cx2], ind2[cx1:cx2] = (
ind2[cx1:cx2].copy(), ind1[cx1:cx2].copy())
return ind1, ind2
def run(self, ngen=int(1E6), mutation_rate=0.3, crossover_rate=0.7):
np.random.seed(seed)
pop, log = algorithms.eaSimple(self.pop, self._tb,
cxpb=crossover_rate,
mutpb=mutation_rate,
ngen=ngen,
stats=self.stats,
halloffame=self.hof)
self.log = log
return self.hof[0].reshape(self.N, self.N), log
if __name__ == "__main__":
np.random.seed(0)
gd = GeneticDetMinimizer()
best, log = gd.run()
It takes about 40 seconds to run 1000 generations on my laptop, which gets me from a minimum determinant value of about -5.7845x108 to -6.41504x1011. I haven't really played around much with the meta-parameters (population size, mutation rate, crossover rate etc.), so I'm sure it's possible to do a lot better.
Here's a greatly improved version that implements a much smarter crossover function that swaps blocks of rows or columns across individuals, and uses a cachetools.LRUCache to guarantee that each mutation step produces a novel configuration, and to skip evaluation of the determinant for configurations that have already been tried:
import numpy as np
import deap
from deap import algorithms, base, tools
import imp
from cachetools import LRUCache
# used to control the size of the cache so that it doesn't exceed system memory
MAX_MEM_BYTES = 11E9
class GeneticDetMinimizer(object):
def __init__(self, N=30, popsize=500, cachesize=None, seed=0):
# an 'individual' consists of an (N^2,) flat numpy array of 0s and 1s
self.N = N
self.indiv_size = N * N
if cachesize is None:
cachesize = int(np.ceil(8 * MAX_MEM_BYTES / self.indiv_size))
self._gen = np.random.RandomState(seed)
# we want the creator module to be local to this instance, since
# creator.create() directly adds new classes to the module's globals()
# (yuck!)
cr = imp.load_module('cr', *imp.find_module('creator', deap.__path__))
self._cr = cr
self._cr.create("FitnessMin", base.Fitness, weights=(-1.0,))
self._cr.create("Individual", np.ndarray, fitness=self._cr.FitnessMin)
self._tb = base.Toolbox()
self._tb.register("attr_bool", self.random_bool)
self._tb.register("individual", tools.initRepeat, self._cr.Individual,
self._tb.attr_bool, n=self.indiv_size)
# the 'population' consists of a list of such individuals
self._tb.register("population", tools.initRepeat, list,
self._tb.individual)
self._tb.register("evaluate", self.fitness)
self._tb.register("mate", self.crossover)
self._tb.register("mutate", self.mutate, rate=0.002)
self._tb.register("select", tools.selTournament, tournsize=3)
# create an initial population, and initialize a hall-of-fame to store
# the best individual
self.pop = self._tb.population(n=popsize)
self.hof = tools.HallOfFame(1, similar=np.array_equal)
# print summary statistics for the population on each iteration
self.stats = tools.Statistics(lambda ind: ind.fitness.values)
self.stats.register("avg", np.mean)
self.stats.register("std", np.std)
self.stats.register("min", np.min)
self.stats.register("max", np.max)
# keep track of configurations that have already been visited
self.tabu = LRUCache(cachesize)
def random_bool(self, *args):
return self._gen.rand(*args) < 0.5
def mutate(self, ind, rate=1E-3):
"""
mutate an individual by bit-flipping one or more randomly chosen
elements
"""
# ensure that each mutation always introduces a novel configuration
while np.packbits(ind.astype(np.uint8)).tostring() in self.tabu:
n_flip = self._gen.binomial(self.indiv_size, rate)
if not n_flip:
continue
idx = self._gen.random_integers(0, self.indiv_size - 1, n_flip)
ind[idx] = ~ind[idx]
return ind,
def fitness(self, individual):
"""
assigns a fitness value to each individual, based on the determinant
"""
h = np.packbits(individual.astype(np.uint8)).tostring()
# look up the fitness for this configuration if it has already been
# encountered
if h not in self.tabu:
fitness = np.linalg.det(individual.reshape(self.N, self.N))
self.tabu.update({h: fitness})
else:
fitness = self.tabu[h]
return fitness,
def crossover(self, ind1, ind2):
"""
randomly swaps a block of rows or columns between two individuals
"""
cx1 = self._gen.random_integers(0, self.N - 2)
cx2 = self._gen.random_integers(cx1, self.N - 1)
ind1.shape = ind2.shape = self.N, self.N
if self._gen.rand() < 0.5:
# row swap
ind1[cx1:cx2, :], ind2[cx1:cx2, :] = (
ind2[cx1:cx2, :].copy(), ind1[cx1:cx2, :].copy())
else:
# column swap
ind1[:, cx1:cx2], ind2[:, cx1:cx2] = (
ind2[:, cx1:cx2].copy(), ind1[:, cx1:cx2].copy())
ind1.shape = ind2.shape = self.indiv_size,
return ind1, ind2
def run(self, ngen=int(1E6), mutation_rate=0.3, crossover_rate=0.7):
pop, log = algorithms.eaSimple(self.pop, self._tb,
cxpb=crossover_rate,
mutpb=mutation_rate,
ngen=ngen,
stats=self.stats,
halloffame=self.hof)
self.log = log
return self.hof[0].reshape(self.N, self.N), log
if __name__ == "__main__":
np.random.seed(0)
gd = GeneticDetMinimizer(0)
best, log = gd.run()
My best score thus far is about -3.23718x1013 -3.92366x1013 after 10000 1000 generations, which takes about 45 seconds on my machine.
Based on the solution cthonicdaemon linked to in the comments, the maximum determinant for a 31x31 Hadamard matrix must be at least 75960984159088×230 ~= 8.1562x1022 (it's not yet proven whether that solution is optimal). The maximum determinant for an (n-1 x n-1) binary matrix is 21-n times the value for an (n x n) Hadamard matrix, i.e. 8.1562x1022 x 2-30 ~= 7.5961x1013, so the genetic algorithm gets within an order of magnitude of the current best known solution.
However, the fitness function seems to plateau around here, and I'm having a hard time breaking -4x1013. Since it's a heuristic search there is no guarantee that it will eventually find the global optimum.
I don't know of any straight-forward method for discrete optimization in scipy. One alternative is using the simanneal package from pip or github, which allows you to introduce your own move function, such that you can restrict it to moves within your domain:
import random
import numpy as np
import simanneal
class BinaryAnnealer(simanneal.Annealer):
def move(self):
# choose a random entry in the matrix
i = random.randrange(self.state.size)
# flip the entry 0 <=> 1
self.state.flat[i] = 1 - self.state.flat[i]
def energy(self):
# evaluate the function to minimize
return -np.linalg.det(self.state)
matrix = np.zeros((5, 5))
opt = BinaryAnnealer(matrix)
print(opt.anneal())
I have looked into this a bit.
A couple of things first off: 1) 56 million is the max value when the size of the matrix is 21x21, not 30x30:https://en.wikipedia.org/wiki/Hadamard%27s_maximal_determinant_problem#Connection_of_the_maximal_determinant_problems_for_.7B1.2C.C2.A0.E2.88.921.7D_and_.7B0.2C.C2.A01.7D_matrices.
But that is also an upper bound on -1, 1 matrices, not 1,0.
EDIT: Reading more carefully from that link:
The maximal determinants of {1, −1} matrices up to size n = 21 are given in the following table. Size 22 is the smallest open case. In the table, D(n) represents the maximal determinant divided by 2n−1. Equivalently, D(n) represents the maximal determinant of a {0, 1} matrix of size n−1.
So that table can be used for upper bounds, but remember they're divided by 2n−1. Also note that 22 is the smallest open case, so trying to find the maximum of a 30x30 matrix has not been done, and is not even close to being done just yet.
2) The reason David Zwicker's code gives an answer of 30 million is probably due to the fact that he's minimising. Not maximising.
return -np.linalg.det(self.state)
See how he's got the minus sign there?
3) Also, the solution space for this problem is very big. I calculate the number of different matrices to be 2^(30*30) i.e. in the order of 10^270. So looking at each matrix is simply impossible, and even look at most of them is too.
I have a bit of code here (adapted from David Zwicker's code) that runs, but I have no idea how close it is to the actual maximum. It takes around 45 mins to do 10 million iterations on my PC, or only about 2 mins for 1 mill iterations. I get a max value of around 3.4 billion. But again, I have no idea how close this is to the theoretical maximum.
import numpy as np
import random
import time
MATRIX_SIZE = 30
def Main():
startTime = time.time()
mat = np.zeros((MATRIX_SIZE, MATRIX_SIZE), dtype = int)
for i in range(MATRIX_SIZE):
for j in range(MATRIX_SIZE):
mat[i,j] = random.randrange(2)
print("Starting matrix:\n", mat)
maxDeterminant = 0
for i in range(1000000):
# choose a random entry in the matrix
x = random.randrange(MATRIX_SIZE)
y = random.randrange(MATRIX_SIZE)
mat[x,y] = 1 - mat[x,y]
#print(mat)
detValue = np.linalg.det(mat)
if detValue > maxDeterminant:
maxDeterminant = detValue
timeTakenStr = "\nTotal time to complete: " + str(round(time.time() - startTime, 4)) + " seconds"
print(timeTakenStr )
print(maxDeterminant)
Main()
Does this help?
I've got a list of about 60 points, from which I'm generating all possible combinations of size 3. I'm trying to determine whether any of the 3 points are within certain distance parameters - that is, not too close to each other, and not too far from each other (say, no point can be less than 10 units to the next nearest point, and no point can be more than 100 units from the furthest point).
I've got code to do so here:
def setPalmCombinations(self):
# Testing confirms that the range of distances is about 5 - 10 for
# minimum distance between points, and anywhere from 150 - 200 for max
# distance between points. 10 and 100 should be reasonable cutoffs.
minDistance = 10
maxDistance = 100
startTime = time.time()
iter = itertools.combinations(self.handContour, 3)
def distanceCheck(points):
A = points[0][0]
B = points[1][0]
C = points[2][0]
AB = np.linalg.norm(A - B)
if not(minDistance < AB < maxDistance): return None
BC = np.linalg.norm(B - C)
if not(minDistance < BC < maxDistance): return None
CA = np.linalg.norm(C - A)
if not(minDistance < CA < maxDistance): return None
return np.array([A, B, C])
a = [distanceCheck(i) for i in iter]
print time.time() - startTime
However, just generating this list of possible combinations takes roughly .4 to .5 seconds, which is far too slow. Is there a way I can optimize this calculation (or fundamentally redo the algorithm so that it's not simply a brute force search) so that I can get the time down to about a realtime calculation (that is, 30ish times a second).
I was thinking about possibly generating a list of the distances between every point, sorting it, and looking for the points with distances toward the center of the cutoff range, but I think that may actually be slower than what I'm doing now.
I'm also trying to figure out some more intelligent algorithm given the fact that the points are originally stored in order of how they define the contour - I should be able to eliminate some points before and after each point in the list when making the combinations ... however, when I tried to generate the combinations using this fact with Python's default for loops, it ended up being slower than simply using itertools and generating everything.
Any advice is appreciated.
Thanks!
I would first calculate the pairwise distance between all points, filter out those that meet your specifications (the dist_min and dist_max) and then check if a combination of three such nodes still matches the criteria (because I'm only checking pairwise - once, for speed).
import itertools
import numpy as np
from scipy.spatial.distance import pdist, squareform
points = np.random.rand(100,2) # could be nodes in 3 dimensions too, but you specify a handpalm, so I'm guessing your contour is merely 2D
def palm_combinations(points, dist_min=.0, dist_max=.05):
dists = pdist(points)
# This matrix lists the distance between each pair of nodes
# (represented by their column and row index, so that the mat[i,j]
# represents the distance between points[i,:], points[j,:])
mat = squareform(dists)
mask = (mat > dist_min) & (mat < dist_max)
two_good_legs = {} # store all combinations of point indices where "two legs" of the triangles have a distance that matches the spec.
myinds = []
for rowind, row in enumerate(mask):
relevant = row[rowind+1:] # distance to a point itself is always zero, hence exclude from list
inds = np.where(relevant)[0] + (rowind+1)
myinds.append(inds)
two_good_legs[rowind] = list(itertools.combinations(inds, 2))
triangles = []
# The only thing left to do now is to check if the third leg's norm is also within spec.
for k,v in two_good_legs.items():
for tup in v:
if tup[1] in myinds[tup[0]]:
triangles.append((k, tup[0], tup[1]))
return triangles
triangles will list all the indices within the points that meet your specifications. If you want to get back to actual coordinates, just get points[triangles,:].
You could visualize all this with:
import maptlotlib.pyplot as plt
plt.triplot(points[:,0], points[:,1], triangles)
plt.plot(points[:,0], points[:,1], 'ko ')
Timing differences between your implementation (modified to exclude the unknown [0] indexing, see my comment) show this:
In [12]: %timeit my_implementation(points, .1, .15)
1 loops, best of 3: 1.58 ms per loop
In [13]: %timeit your_implementation(points, .1, .15)
1 loops, best of 3: 2.32 s per loop
With points being of the shape (100,2), so about a 1470x speed increase on my machine.
You could increase the speed slightly, if you get rid of the call to squareform, which is the bottleneck according to the profiler, and it isn't strictly necessary but it makes the code more readable ("Readability counts." cfr. the Zen of Python, by Tim Peters). For that you would make the following changes:
def palm_combinations(points, dist_min=.0, dist_max=.05):
dists = pdist(points)
mask = (dists > dist_min) & (dists < dist_max)
two_good_legs, myinds, triangles = {}, [], []
s = points.shape[0]
ind_u1, ind_un = 0, s - 1
for rowind in xrange(s-1):
relevant = mask[ind_u1:ind_un]
ind_u1 = ind_un
ind_un += s - 1*(rowind+2)
inds = np.where(relevant)[0] + (rowind+1)
myinds.append(inds)
two_good_legs[rowind] = list(itertools.combinations(inds, 2))
for k,v in two_good_legs.items():
for tup in v:
if tup[1] in myinds[tup[0]]:
triangles.append((k, tup[0], tup[1]))
return triangles
However, the speed gains won't be noticeable for small datasets.
You can speedup using numpy. Assuming comb is your already calculated possible combinations, you can easily vectorize distance calculations:
>>> import numpy as np
>>> comb = np.array(list(comb))
>>> dists = comb - np.roll(comb, -1)
# L2 norm
>>> dists = dists**2
dists will be a Nx3 array containing AB BC and CA distances respectively.
Mask it with your conditions:
>>> mask = (dists > minDist) & (dists < maxDist)
Find rows where all distances satisfy the conditon:
>>> rows = np.all(mask, axis=1)
return combinations that satisfy conditions:
>>> comb[rows]
UPDATE: Just noticed that this is still quite slow for large N. Here it is the function that contains all the above steps:
>>> def palm_combinations(points, min_dist=10, max_dist=100):
combs = np.array(list(itertools.combinations(points, 3)))
dists = (combs - np.roll(combs, -1))**2
mask = (dists > min_dist) & (dists < max_dist)
return combs[np.all(mask, axis=1)]
For a sample data of 100 points:
>>> a = np.random.rand(100) * 500
>>> %timeit palm_combinations(a)
10 loops, best of 3: 79.5 ms per loop
The bottleneck of the function is the generation of all the combinations:
>>> %timeit combs = np.array(list(itertools.combinations(a, 3)))
10 loops, best of 3: 69.2 ms per loop
UPDATE 2: It can be speeded-up with a bit more complicated approach. Using np.fromiter:
# Define combinations data type (3 float each combination)
>>> dt = np.dtype('f,f,f')
# Fast numpy array conversion
>>> combs = np.fromiter(itertools.combinations(points, 3), dt)
The function would be something like:
>>> def palm_combinations(points, min_dist=10, max_dist=100):
dt = np.dtype('f,f,f')
combs = np.fromiter(itertools.combinations(points, 3), dt)
combs = combs.view('f').reshape(-1,3)
dists = (combs - np.roll(combs, -1))**2
mask = (dists > min_dist) & (dists < max_dist)
return combs[np.all(mask, axis=1)]
And the speed test:
>>> %timeit palm_combinations(a)
10 loops, best of 3: 29.8 ms per loop
For N=100
If the number of contour points is very large you might want to try an heuristic approach.
That is take three random points, (check min and max distances,) calculate the radius of the corresponding circle, compare to previous calculated radius, repeat. You would have to figure in some kind of stopping criteria, like max number of iterations, or no better radius found over the last x iterations, or radii growth rate below x percent, or some combination of those.
Apart from that, in regards to your clarification comment under your question:
Either your terminology or your approach is off.
There is no guaranty that a circle going through thee of the contour points is inside the convex hull of the contour points. Imagine for example a spread hand, the shape will have concave segments, if all three points are within one such segment the circle will lie outside the hand.
What I think you mean is the circle inscribed within the convex hull of each three point set.