"""
Contains all the mathematical helper functions used throughout the scaffold.
Differs from helpers.py only categorically. Helpers.py contains functions,
classes and general logic that supports the scaffold, while functions.py
contains a collection of mathematical functions.
"""
import bisect
import numpy as np
import random
from scipy.spatial import distance
[docs]def compute_circle(center, radius, n_samples=50):
"""
Create `n_samples` points on a circle based on given `center` and `radius`.
:param center: XYZ vector of the circle center
:type center: array-like
:param radius: Radius of the circle
:type radius: scalar value
:param n_samples: Amount of points on the circle.
:type n_samples: int
"""
nodes = np.linspace(0, 2 * np.pi, n_samples, endpoint=False)
x, y = np.cos(nodes) * radius + center[0], np.sin(nodes) * radius + center[1]
return np.column_stack([x, y])
[docs]def apply_2d_bounds(possible_points, cell_bounds):
"""
Compare a 2xN matrix of XZ coordinates to a matrix 2x3 with a minimum column and maximum column of XYZ coordinates.
"""
x_mask = (possible_points[:, 0].__ge__(cell_bounds[0, 0])) & (
possible_points[:, 0].__le__(cell_bounds[0, 1])
)
z_mask = (possible_points[:, 1].__ge__(cell_bounds[2, 0])) & (
possible_points[:, 1].__le__(cell_bounds[2, 1])
)
return possible_points[x_mask & z_mask]
[docs]def get_candidate_points(center, radius, bounds, min_ϵ, max_ϵ, return_ϵ=False):
"""
Returns a list of points that are suited next candidates in a random walk.
Computes a circle of points between `2r + ϵ` distance away from the center
and removes any points that lie outside of the given bounds.
:param center: 2D position of the starting point.
:type center: list
:param radius: Unit distance radius of the particle at the center point.
:type radius: float
:param bounds: A 2x3 matrix where the first column are the minimum XYZ
and the last column the maximum XYZ.
:type bounds: ndarray
:param min_ϵ: Lower bound of epsilon used to calculate random distance.
:type min_ϵ: float
:param max_ϵ: Upper bound of epsilon used to calculate random distance.
:type max_ϵ: float
:param return_ϵ: If `True` the candidates and ϵ used to calculate them
will be returned as a tuple.
"""
# Determine the uniformly random ϵ
rnd_ϵ = np.random.uniform(min_ϵ, max_ϵ)
# Create a circle of points `2r + ϵ`
possible_points = compute_circle(center, radius * 2 + rnd_ϵ)
# Get only the candidates within the bounds
candidates = apply_2d_bounds(possible_points, bounds)
if return_ϵ:
return candidates, rnd_ϵ
return candidates
[docs]def exclude_index(arr, index):
"""
Return a new list with the element at `index` removed.
"""
return [arr[i] for i in range(len(arr)) if i != index]
[docs]def add_y_axis(points, min, max):
"""
Add random values to the 2nd column of a matrix of 2D points.
"""
return np.insert(points, 1, np.random.uniform(min, max, points.shape[0]), axis=1)
#############################
# Gist from: https://gist.github.com/fjsj/9c9f7f36cfd3205343e333d86778433c
#
def bisect_index(arr, start, end, x):
i = bisect.bisect_left(arr, x, lo=start, hi=end)
if i != end and arr[i] == x:
return i
return -1
def exponential_search(arr, start, x):
if x == arr[start]:
return 0
i = start + 1
while i < len(arr) and arr[i] <= x:
i = i * 2
return bisect_index(arr, i // 2, min(i, len(arr)), x)
def compute_intersection_list(l1, l2):
# find B, the smaller list
B = l1 if len(l1) < len(l2) else l2
A = l2 if l1 is B else l1
# run the algorithm described at:
# https://stackoverflow.com/a/40538162/145349
i = 0
j = 0
intersection_list = []
for i, x in enumerate(B):
j = exponential_search(A, j, x)
if j != -1:
intersection_list.append(x)
else:
j += 1
return intersection_list
[docs]def compute_intersection_slice(l1, l2):
"""
Returns the indices of elements in l1 that intersect with l2.
"""
# find B, the smaller list
swapped = len(l1) < len(l2)
B = l1 if swapped else l2
A = l2 if l1 is B else l1
# run the algorithm described at:
# https://stackoverflow.com/a/40538162/145349
i = 0
j = 0
intersection_list = []
for i, x in enumerate(B):
j = exponential_search(A, j, x)
if j != -1:
intersection_list.append(i if swapped else j)
else:
j += 1
return intersection_list
# Stolen from abandoned neuronpy project. By Tom McCavish
[docs]def poisson_train(frequency, duration, start_time=0, seed=None):
"""
Generator function for a Homogeneous Poisson train.
:param frequency: The mean spiking frequency.
:param duration: Maximum duration.
:param start_time: Timestamp.
:param seed: Seed for the random number generator. If None, this will be
decided by numpy, which chooses the system time.
:return: A relative spike time from t=start_time, in seconds (not ms).
EXAMPLE::
# Make a list of spikes at 20 Hz for 3 seconds
spikes = [i for i in poisson_train(20, 3)]
"""
cur_time = start_time
end_time = duration + start_time
rangen = np.random.mtrand.RandomState()
if seed is not None:
rangen.seed(seed)
isi = 1.0 / frequency
while cur_time <= end_time:
cur_time += isi * rangen.exponential()
if cur_time > end_time:
return
yield cur_time
[docs]def get_distances(candidates, point):
"""
Return the distances of a list of points to a common point
"""
return [np.sqrt(np.sum((np.array(c) - point) ** 2)) for c in candidates]