Spaces:
Sleeping
Sleeping
import numpy as np | |
def generate_piecewise_linear_function(num_pieces, lower, upper, delta = 0.3): | |
""" | |
Generates a piece-wise linear function on the interval (lower, upper) that is 2*pi-periodic. | |
Args: | |
num_pieces (int): Number of linear pieces in the function. | |
lower (float): The lower range of the interval | |
upper (float): The upper range of the interval | |
delta (float): Parameter determining how rapidly the function varies between the grid points (i.e., modulates | |
the slopes of the piecewise functions). Larger values mean more variability. Default is 0.3. | |
Returns: | |
function: A piece-wise linear function. | |
""" | |
# Generate equally spaced points in the interval (-pi, pi) | |
x_points = np.linspace(lower, upper, num_pieces + 1) | |
# Generate random y-values for each point | |
y_points = np.zeros(num_pieces + 1) | |
y_points[0] = np.random.uniform(-1, 1) | |
for n in range(1, y_points.shape[0]): | |
y_points[n] = y_points[n - 1] + 0.3 * np.random.uniform(-1, 1) | |
y_points[0] = y_points[-1] | |
min_y = y_points.min() | |
# ensure y values are nonegative | |
if min_y < 0: | |
y_points -= min_y | |
y_points += np.random.uniform(0, 0.5) # random (constant) offset | |
def piecewise_linear(x): | |
""" | |
Evaluates the piece-wise linear function at a given x. | |
Args: | |
x (float): The x-coordinate at which to evaluate the function. | |
Returns: | |
float: The y-coordinate of the function at x. | |
""" | |
for i in range(num_pieces): | |
if x_points[i] <= x < x_points[i + 1]: | |
# Linear interpolation between the two points | |
slope = (y_points[i + 1] - y_points[i]) / (x_points[i + 1] - x_points[i]) | |
return slope * (x - x_points[i]) + y_points[i] | |
return y_points[0] # For x = pi | |
return piecewise_linear | |
def generate_piecewise_constant_function(num_pieces, lower, upper): | |
""" | |
Generates a piece-wise constant function on the interval (lower, upper) that is 2*pi periodic. | |
Args: | |
num_pieces (int): Number of constant pieces in the function. | |
lower (float): The lower range of the interval | |
upper (float): The upper range of the interval | |
Returns: | |
function: A piece-wise constant function. | |
""" | |
# Generate equally spaced points in the interval (-pi, pi) | |
x_points = np.linspace(lower, upper, num_pieces + 1) | |
# Generate random y-values for each constant piece | |
y_values = np.random.rand(num_pieces) * 2 - 0 # Random values between 0 and 1 | |
# Ensure the function is 2*pi periodic | |
y_values = np.append(y_values, y_values[0]) | |
def piecewise_constant(x): | |
""" | |
Evaluates the piece-wise constant function at a given x. | |
Args: | |
x (float): The x-coordinate at which to evaluate the function. | |
Returns: | |
float: The y-coordinate of the function at x. | |
""" | |
for i in range(num_pieces): | |
if x_points[i] <= x < x_points[i + 1]: | |
return y_values[i] | |
return y_values[0] # For x = pi | |
return piecewise_constant | |
def generate_piecewise_bc(x2_grid, x3_grid, num_pieces): | |
""" | |
Generates a piecewise linear function on the domain defined by x2_grid and x3_grid. | |
Args: | |
x2_grid (numpy.ndarray): A 2D array, x2 grid values | |
x3_grid (numpy.ndarray): A 2D array, x3 grid values | |
num_pieces (int): The number of pieces in the piecewise linear function. | |
Returns: | |
numpy.ndarray: A 2D array representing the piecewise linear function | |
""" | |
x2_fun = generate_piecewise_linear_function(num_pieces, lower = x2_grid.min(), upper = x2_grid.max()) | |
x3_fun = generate_piecewise_linear_function(num_pieces, lower = x3_grid.min(), upper = x3_grid.max()) | |
x2vals_1d = np.zeros_like(x2_grid[:, 0]) | |
x3vals_1d = np.zeros_like(x3_grid[0, :]) | |
for i in range(x2vals_1d.shape[0]): | |
x2vals_1d[i] = x2_fun(x2_grid[i, 0]) | |
for i in range(x3vals_1d.shape[0]): | |
x3vals_1d[i] = x3_fun(x3_grid[0, i]) | |
x2vals_2d, x3vals_2d = np.meshgrid(x2vals_1d, x3vals_1d, indexing = 'ij') | |
return x2vals_2d * x3vals_2d | |
def random_2d_gaussian(theta, phi): | |
""" | |
Generates a 2D Gaussian G(x,y), where | |
x = np.cos(0.5 * freq_x * theta - phase_x) | |
y = np.cos(0.5 * freq_y * phi - phase_y) | |
Here, the frequencies and phases are randomly sampled, and (theta, phi) define a 2D meshgrid. | |
Args: | |
theta (numpy.ndarray): 2D array, meshgrid of the first coordinate | |
phi (numpy.ndarray): 2D array, meshgrid of the second coordinate | |
Returns: | |
numpy.ndarray: A 2D array representing the values of the Gaussian on the grid. | |
""" | |
phase_x = np.random.uniform(0, 2 * np.pi) | |
phase_y = np.random.uniform(0, 2 * np.pi) | |
freq_x = np.random.randint(1, 2) | |
freq_y = np.random.randint(1, 2) | |
x = np.cos(0.5 * freq_x * theta - phase_x) | |
y = np.cos(0.5 * freq_y * phi - phase_y) | |
sigma_x = np.random.uniform(0.1, 3.0) | |
sigma_y = np.random.uniform(0.1, 1.0) | |
rho = 0 | |
covariance_matrix = np.array([[sigma_x**2, rho * sigma_x * sigma_y], | |
[rho * sigma_x * sigma_y, sigma_y**2]]) | |
inv_sigma_xx = 1.0 / sigma_x**2 | |
inv_sigma_yy = 1.0 / sigma_y**2 | |
inv_sigma_xy = -rho / (sigma_x * sigma_y) | |
if np.any(np.linalg.eigvals(covariance_matrix) < 0): | |
raise ValueError('Covariance matrix is not positive semi-definite.') | |
def gaussian_2d(x, y): | |
return np.exp(-0.5 * (inv_sigma_xx * x**2 + inv_sigma_yy * y**2 + 2 * inv_sigma_xy * x * y)) | |
gaussian_values = gaussian_2d(x, y) | |
return gaussian_values | |
def generate_random_functions(N, X, Y, num_terms = 16, min_freq = 1, max_freq = 16, func_gen_id = 0): | |
""" | |
Generates N random 2pi-periodic functions on a 2D grid as a Fourier series, with different types of | |
modulation applied to the amplitudes. | |
Args: | |
N (int): Number of functions to generate. | |
X (numpy.ndarray): 2D array representing the values of the first coordinate on the grid | |
Y (numpy.ndarray): 2D array representing the values of the second coordinate on the grid | |
num_terms (int, optional): Number of terms in the Fourier series expansion. Default is 16. | |
min_freq (int, optional): Minimum frequency for the Fourier series terms. Default is 1. | |
max_freq (int, optional): Maximum frequency for the Fourier series terms. Default is 16. | |
func_gen_id (int, optional): Type of function to generate based on the decay of the expansion coefficients | |
as frequency is increased. Values can range from -1 to 4. Default is 0. | |
Returns: | |
numpy.ndarray: A 3D numpy array of shape (N, nx, ny) containing the function values. | |
Raises: | |
ValueError: If max_freq is less than min_freq or if an invalid func_gen_id is provided. | |
""" | |
# Check if the maximum frequency is less than the minimum frequency | |
if max_freq < min_freq: | |
raise ValueError('max_freq cannot be less than min_freq') | |
# Generate uniformly distributed functions if func_gen_id is -1 | |
if func_gen_id == -1: | |
F_batch = np.random.uniform(0, 1, size = (N,) + X.shape) | |
return F_batch | |
# Initialize the batch of functions with zeros | |
F_batch = np.zeros((N,) + X.shape) | |
# Loop through each function to be generated | |
for n in range(N): | |
# Add a cosine term with a half frequency with 20% chance | |
if np.random.uniform(0, 1) < 0.2: | |
amp_cos_half = np.random.uniform(0, 1) # Amplitude for cosine term | |
phase_cos_half = np.random.uniform(0, 2 * np.pi) # Phase shift for cosine term | |
F_batch[n] += amp_cos_half * np.cos(0.5 * X - phase_cos_half) | |
# Fourier series | |
for _ in range(num_terms): | |
amplitude = np.random.uniform(-1, 1) # Random amplitude for y-component | |
kx, ky = np.random.randint(min_freq, max_freq + 1, 2) # Frequencies for x and y components | |
phase_x = np.random.uniform(0, 2 * np.pi) # Phase shift for x-component | |
phase_y = np.random.uniform(0, 2 * np.pi) # Phase shift for y-component | |
# Determine the coefficient amplitude based on the func_gen_id | |
if func_gen_id == 0: | |
# No decay applied to amplitude | |
pass | |
elif func_gen_id == 1: | |
if np.random.uniform(0, 1) < 0.5: | |
amplitude = amplitude / kx | |
else: | |
amplitude = amplitude / ky | |
elif func_gen_id == 2: | |
amplitude = amplitude / (kx * ky) | |
elif func_gen_id == 3: | |
amplitude = amplitude / (kx * kx * ky * ky) | |
elif func_gen_id == 4: | |
# Gaussian decay with random covariance matrix | |
sxx = np.random.uniform(0.1, 1.0) | |
syy = np.random.uniform(0.1, 1.0) | |
sxy = np.random.uniform(0.1, 1.0) | |
amplitude = amplitude * np.exp(-(sxx * kx**2 + syy * ky**2 + sxy * kx * ky)) | |
else: | |
raise ValueError( | |
f'Invalid func_gen_id. Should be an integer in the range [-1, 4], but received {func_gen_id}') | |
# Add the term to the nth function in the batch | |
F_batch[n] += amplitude * np.cos(kx * X - phase_x) * np.cos(ky * Y - phase_y) | |
# Adjust the function to ensure it's positive | |
minF = np.min(F_batch[n]) | |
if minF < 0: | |
F_batch[n] -= minF | |
return F_batch | |