Spaces:
Running
Running
import numpy as np | |
import torch | |
# Universal constants | |
C = 340. * 100. # Speed of sound in air (cm/s) | |
def compute_length_of_air_column_cylindrical( | |
timestamps, duration, height, b, **kwargs, | |
): | |
""" | |
Randomly chooses a l(t) curve satisfying the two point equations. | |
""" | |
L = height * ( (1 - np.exp(b * (duration - timestamps))) / (1 - np.exp(b * duration)) ) | |
return L | |
def compute_axial_frequency_cylindrical( | |
lengths, radius, beta=0.62, mode=1, **kwargs, | |
): | |
""" | |
Computes axial resonance frequency for cylindrical container at given timestamps. | |
""" | |
if mode == 1: | |
harmonic_weight = 1. | |
elif mode == 2: | |
harmonic_weight = 3. | |
elif mode == 3: | |
harmonic_weight = 5. | |
else: | |
raise ValueError | |
# Compute fundamental frequency curve | |
F0 = harmonic_weight * (0.25 * C) * (1. / (lengths + (beta * radius))) | |
return F0 | |
def compute_axial_frequency_bottleneck( | |
lengths, radius, height, Rn, Hn, beta_bottle=(0.6 + 8/np.pi), **kwargs, | |
): | |
# Here, R and H are base radius and height of the bottleneck | |
eps = 1e-6 | |
kappa = (0.5 * C / np.pi) * (Rn/radius) * np.sqrt(1 / (Hn + beta_bottle * Rn)) | |
frequencies = kappa * np.sqrt(1 / (lengths + eps)) | |
return frequencies | |
def compute_f0_cylindrical(Y, rho_g, a, R, H, mode=1, **kwargs,): | |
if mode == 1: | |
m = 1.875 | |
n = 2 | |
elif mode == 2: | |
m = 4.694 | |
n = 3 | |
elif mode == 3: | |
m = 7.855 | |
n = 4 | |
else: | |
raise ValueError | |
term = ( ((n**2 - 1)**2) + ((m * R/H)**4) ) / (1 + (1./n**2)) | |
f0 = (1. / (12 * np.pi)) * np.sqrt(3 * Y / rho_g) * (a / (R**2)) * np.sqrt(term) | |
return f0 | |
def compute_xi_cylindrical(rho_l, rho_g, R, a, **kwargs,): | |
""" | |
Different papers use different multipliers. | |
For us, using 12. * (4./9.) works best empirically. | |
""" | |
xi = 12. * (4. / 9.) * (rho_l/rho_g) * (R/a) | |
return xi | |
def compute_radial_frequency_cylindrical( | |
heights, R, H, Y, rho_g, a, rho_l, power=3, mode=1, **kwargs, | |
): | |
""" | |
Computes radial resonance frequency for cylindrical. | |
Args: | |
heights (np.ndarray): height of liquid at pre-defined time stamps | |
""" | |
# Only f0 changes for higher modes | |
f0 = compute_f0_cylindrical(Y, rho_g, a, R, H, mode=mode) | |
xi = compute_xi_cylindrical(rho_l, rho_g, R, a) | |
frequencies = f0 / np.sqrt(1 + xi * ((heights/H) ** power) ) | |
return frequencies | |
def compute_slant_lengths_semiconical( | |
timestamps, duration, r_top, r_bot, height, **kwargs, | |
): | |
# Top radius / base radius | |
rf = r_bot / r_top | |
# Time fraction | |
tf = timestamps/duration | |
# Height fractions: h(t) / H | |
height_fractions = (1. / (rf - 1)) * (np.cbrt(((rf**3 - 1) * (tf)) + 1) - 1) | |
# Slant air column lengths | |
heights = height_fractions * height | |
slant_lengths = np.sqrt(1 - ((r_top - r_bot) / height)**2) * (height - heights) | |
return slant_lengths | |
def compute_axial_frequency_semiconical(slant_lengths, r_top, r_bot, beta=1.28, **kwargs): | |
""" | |
Computes axial resonance frequency for cylinder. | |
Args: | |
slant_lengths (np.ndarray): slant length of air column | |
r_top (float): top radius | |
r_bot (float): base radius | |
beta (float): end correction coefficient | |
""" | |
frequencies_axial = (C / 2) * (1 / (slant_lengths + (beta * (r_bot + r_top)))) | |
return frequencies_axial | |
def get_frequencies( | |
t, | |
params, | |
container_shape="cylindrical", | |
harmonic=None, | |
vibration_type="axial", | |
semiconical_as_cylinder=False, | |
): | |
""" | |
Computes requires frequency f(t) for given t. | |
""" | |
if container_shape == "semiconical": | |
# Makes an assumption that semiconical shape is similar to cylindrical | |
if semiconical_as_cylinder: | |
container_shape = "cylindrical" | |
if (container_shape == "cylindrical") or (container_shape == "bottleneck_as_cylindrical"): | |
# Compute length of air column first | |
lengths = compute_length_of_air_column_cylindrical(t, **params) | |
if vibration_type == "axial": | |
frequencies = compute_axial_frequency_cylindrical(lengths, **params) | |
if harmonic is not None: | |
assert harmonic > 0 and isinstance(harmonic, int) | |
frequencies = frequencies * harmonic | |
elif vibration_type == "radial": | |
if harmonic is None: | |
mode = 1 | |
else: | |
assert isinstance(harmonic, int) | |
assert harmonic in [1, 2] | |
mode = harmonic + 1 | |
frequencies = compute_radial_frequency_cylindrical( | |
lengths, mode=mode, **params, | |
) | |
else: | |
raise NotImplementedError | |
elif container_shape == "semiconical": | |
# Compute length of air column first | |
slant_lengths = compute_slant_lengths_semiconical(t, **params) | |
if vibration_type == "axial": | |
frequencies = compute_axial_frequency_semiconical( | |
slant_lengths, **params, | |
) | |
if harmonic is not None: | |
assert harmonic > 0 and isinstance(harmonic, int) | |
frequencies = frequencies * harmonic | |
else: | |
raise NotImplementedError | |
elif container_shape == "bottleneck": | |
# Compute length of air column first assuming | |
# base of the bottle is a cylindrical | |
lengths = compute_length_of_air_column_cylindrical(t, **params) | |
if vibration_type == "axial": | |
frequencies = compute_axial_frequency_bottleneck( | |
lengths, **params, | |
) | |
if harmonic is not None: | |
assert harmonic > 0 and isinstance(harmonic, int) | |
frequencies = frequencies * harmonic | |
else: | |
raise NotImplementedError | |
else: | |
raise ValueError | |
return frequencies | |
def get_params(row, semiconical_as_cylinder=False): | |
m = row["measurements"] | |
duration = row["end_time"] - row["start_time"] | |
params = dict(duration=duration) | |
if row["shape"] == "cylindrical": | |
radius = 0.25 * (m["diameter_top"] + m["diameter_bottom"]) | |
height = m["net_height"] | |
params.update( | |
height=height, | |
radius=radius, | |
beta=row.get("beta", 0.62), | |
# Constant flow | |
b=0.01, | |
) | |
elif row["shape"] == "semiconical": | |
if semiconical_as_cylinder: | |
# Assume semiconical shape as cylindrical | |
radius = 0.25 * (m["diameter_top"] + m["diameter_bottom"]) | |
height = m["net_height"] | |
params.update( | |
height=height, | |
radius=radius, | |
beta=0.62, | |
# Constant flow | |
b=0.01, | |
) | |
else: | |
r_top = 0.5 * m["diameter_top"] | |
r_bot = 0.5 * m["diameter_bottom"] | |
height = m["net_height"] | |
beta = 1.28 | |
params.update( | |
r_top=r_top, | |
r_bot=r_bot, | |
height=height, | |
beta=beta, | |
) | |
elif row["shape"] == "bottleneck": | |
radius = 0.5 * m["diameter_bottom"] | |
Rn = 0.5 * m["diameter_top"] | |
Hn = m["neck_height"] | |
height = m["net_height"] - Hn | |
params.update( | |
height=height, | |
radius=radius, | |
Rn=Rn, | |
Hn=Hn, | |
# Constant flow | |
b=0.01, | |
) | |
elif row["shape"] == "bottleneck_as_cylindrical": | |
# Approximates bottleneck as cylindrical | |
radius = 0.5 * m["diameter_bottom"] | |
height = m["net_height"] + m["neck_height"] | |
params.update( | |
height=height, | |
radius=radius, | |
beta=row.get("beta", 0.62), | |
# Constant flow | |
b=0.01, | |
) | |
else: | |
raise ValueError | |
return params | |
def frequency_to_wavelength(f): | |
""" | |
Converts frequency to wavelength. | |
Args: | |
f (float): frequency | |
""" | |
return C / f | |
def wavelength_to_frequency(l): | |
""" | |
Converts wavelength to frequency. | |
Args: | |
l (float): wavelength | |
""" | |
return C / l | |
def get_cylinder_radius(m): | |
return 0.25 * (m['diameter_top'] + m['diameter_bottom']) | |
def get_cylinder_height(m): | |
return m['net_height'] | |
def get_flow_rate(m, duration): | |
r = get_cylinder_radius(m) | |
h = get_cylinder_height(m) | |
volume = np.pi * (r**2) * h | |
q = volume / duration | |
return q | |
def get_length_of_air_column(m, duration, timestamps): | |
h = get_cylinder_height(m) | |
l = (-h/duration) * timestamps + h | |
l = torch.from_numpy(l) | |
return l | |
def estimate_cylinder_radius(wavelengths, timestamps=None, beta=0.62): | |
radius_pred = ((1. / beta) * (wavelengths[-1] / 4.)).item() | |
return radius_pred | |
def estimate_cylinder_height(wavelengths, timestamps=None, beta=0.62): | |
height_pred = wavelengths[0] / 4. - wavelengths[-1] / 4. | |
return height_pred.item() | |
def estimate_flow_rate(wavelengths, timestamps=None, output_fps=49.): | |
radius = estimate_cylinder_radius(wavelengths) | |
l_pred = (wavelengths - wavelengths[-1]) / 4. | |
slope = np.gradient(l_pred).mean() * output_fps | |
Q_pred = -np.pi * (radius**2) * slope | |
return Q_pred | |
def estimate_length_of_air_column(wavelengths, timestamps=None): | |
l_pred = (wavelengths - wavelengths[-1]) / 4. | |
return l_pred | |