
La ley de Hooke
1 Introducción
La ley de Hooke es una ley empírica que dice que la fuerza F_e necesaria para comprimir o estirar un resorte cuya longitud natural es L_0 hasta una longitud L es proporcional a la deformación L - L_0:
F_e = k \, (L - L_0) \tag{1}
donde k es la constante elástica del resorte. Esta ley se puede considerar como una aproximación de primer orden de la fuerza de un resorte alrededor de su longitud natural:
F_e(L) = F_e(L_0) + \left.\frac{dF_e}{dL}\right\rvert_{L_0} (L - L_0) + \ldots
donde \left.\frac{dF_e}{dL}\right\rvert_{L_0} = k.
1.1 Caso estático
Una forma de medir la constante elástica k de un resorte es colgando una masa m de este (Figura 1). Cuando el sistema se encuentre en equilibrio, la fuerza de gravedad F_g se compensará con la fuerza elástica F_e:
\begin{align*} F_g &= F_e \\ m g &= k (x_{eq} - x_0) \end{align*} \tag{2}
En este caso, x_{eq} - x_0 es el estiramiento respecto de la longitud natural, y g es la aceleración de la gravedad. Midiendo este estiramiento y la fuerza, es decir, la masa y g, se puede determinar la constante k.
1.2 Caso dinámico
Otra forma de medir la constante elástica es a través de su movimiento al realizar una perturbación de su equilibrio (Figura 2). Si se aparta la masa una distancia d de la posición de equilibrio x_{eq}, su posición x en función del tiempo t será:
x(t) = d \, \cos(\omega t) + x_{eq} \tag{3}
donde
\omega^2 = \frac{k}{m} \tag{4}
La velocidad v(t), la aceleración a(t) y la fuerza F(t) del sistema también son sinusoidales:
F(t) = m \, a(t) = m \frac{d^2x}{dt^2}(t) = - m \omega^2 d \, \cos(\omega t) \tag{5}
Midiendo el periodo \tau = \frac{2 \pi}{\omega} y la masa, se puede determinar k.
2 Medición de la masa
Para colgar del resorte, se dispone de pesas de distinta masa la cual se puede determinar con la balanza que se encuentra en el laboratorio. Para obtener masas más grandes, hay que combinar varias de estas masas. Analice si conviene:
- pesar todas las masas por separado y después sumar las que se utilicen en cada medición;
- para cada medición, pesar todas las masas juntas.
3 Caso estático
Realicen mediciones de la posición de equilibrio del resorte x_{eq} variando la masa m que cuelga de él. A partir de esos datos, encuentren la constante elástica del resorte utilizando el modelo dado por la ecuación Ecuación 2.
3.1 Opción 1: x vs. m
No es posible determinar simultáneamente k y g a partir del ajuste, independientemente de si se usa la relación
x(m) = \frac{g}{k} \cdot m
o la forma inversa
m(x) = \frac{k}{g} \cdot x.
Como el ajuste no cambia si se multiplican k y g por el mismo factor, solo puede determinarse la relación A = k/g. Luego, se puede obtener k usando una medición independiente de g.
¿Se cumple la condición \sigma_y \gg |\frac{df}{dx}| \sigma_x para considerar únicamente el error en y? (Bevington y Robinson 2003, 102)
¿Hace falta incluir una ordenada al origen en el ajuste? En ese caso, ¿a qué puede deberse? ¿Se obtiene el mismo valor si se mide independientemente?
Al igual que con el promedio, al usar más mediciones se reduce el error en los parámetros (A, en este caso) del ajuste. Entonces, ¿se puede reducir el error de k a 0? Si no, ¿hasta cuanto se podría?
Compare el error asignado a la variable del eje y con la desviación estándar de los residuos. Realicen un histograma de los residuos. ¿Es gaussiano?
Extra
Bajo ciertas condiciones (Widrow et al. 1996), una medición redondeada puede modelarse como la suma de la medición sin redondear más un error con distribución uniforme de ancho a = 10^{-d}, donde d es la cantidad de decimales. La desviación estándar de una distribución uniforme de ancho a es a / \sqrt{12}.3.2 Opción 2: x vs. F
Extra
En este método, las fuerzas no son independientes y aparecen correlaciones. Si no se tienen en cuenta, se subestima el error en k. Si se consideran correctamente, se obtiene el mismo resultado que en la opción 1.
Una alternativa es ajustar la fuerza de la gravedad F_g = m g como función de la posición x (o al revés):
F_g = k x.
En este caso, se obtendría k directamente del ajuste.
Como en el punto anterior, se usa una medición independiente de g para convertir las masas en fuerzas:
\begin{align*} \begin{cases} &m_1 &\mapsto &F_1 = m_1 \cdot g \\ & &\vdots \\ &m_N &\mapsto &F_N = m_N \cdot g \end{cases} \end{align*}
Como estas fuerzas reutilizan la misma medición de g, están correlacionadas. Si la medición de g resulta menor (o mayor) que el valor real, todas las fuerzas serán menores (o mayores) que los valores reales. Es decir, sus errores no son independientes.
Cuando existen correlaciones, no basta con conocer el error de cada medición, sino que también hace falta conocer los “errores conjuntos” entre mediciones (ver errores dependientes). Se puede usar el paquete uncertainties para calcular la matriz de covarianza que describe esas correlaciones:
import labo1
import uncertainties
import uncertainties.unumpy
# Datos x y m
x = ...
m = ...
sigma_m = ...
# Agrego incertezas y calculo F
g = uncertainties.ufloat(9.80, 0.01)
m = uncertainties.unumpy.uarray(m, sigma_m)
F = m * g
# Ajusto
def func(x, k):
return k * x
labo1.curve_fit(
func,
x=x,
y=uncertainties.unumpy.nominal_values(F),
# Elegir `y_err`:
# (✅ correcto) utilizando la matriz de covarianza
# y_err=uncertainties.covariance_matrix(F),
# (❌ incorrecto) utilizando las incertezas
# es decir, la raíz de la diagonal de la matriz de covarianza
# y_err=uncertainties.unumpy.std_devs(F),
)Compare el resultado del caso correcto, con la matriz de covarianza, con el obtenido con el método anterior.
Analicen cómo cambian los resultados al variar el error en g.
4 Caso dinámico
Usando un sensor de fuerza, midan la fuerza F en función del tiempo t al perturbar el sistema a partir de su equilibrio. Luego, midan \tau = \frac{2\pi}{\omega} para distintas masas m y determinen k a partir del modelo de las ecuaciones 3 y 4.
4.1 Sensor de fuerza
El sensor de fuerza mide un voltaje proporcional a la fuerza: V = a \cdot F + V_0 \tag{6} donde a tiene unidades de fuerza sobre voltaje. La señal se digitaliza con un conversor analógico-digital (SensorDAQ), que toma muestras de la señal analógica cada cierto período T (Figura 3). La frecuencia de muestreo f = 1/T puede configurarse hasta 48 kHz, esto es, 48 mil muestras por segundo. Puede considerarse despreciable el error en el tiempo que se asigna a cada muestra (ver manual).
Para tomar las mediciones en la computadora, hay que utilizar el programa MotionDAQ. El archivo que exporta se puede cargar con:
import pandas as pd
pd.read_csv(
"archivo.txt",
delimiter="\t", # las columas están separadas por "tab"
skiprows=3, # ignorar las 3 primeras filas, que tienen metadata
decimal="," # opcional: los números usa coma decimal, en lugar de punto
)4.2 Determinar la frecuencia o el periodo
4.2.1 Opción 1: ajuste por cuadrados mínimos
Usando el modelo de la fuerza en función del tiempo (Ecuación 5) y teniendo en cuenta que el sensor mide un voltaje proporcional a la fuerza (Ecuación 6), determinen la frecuencia \omega o el período \tau ajustando la expresión
V(t) = A \, \cos(\omega t + \phi) + B \tag{7}
donde \{A, \omega, \phi, B\} son los parámetros a determinar.
- Teniendo en cuenta las ecuaciones Ecuación 5 y Ecuación 6, ¿qué significado físico tienen los distintos parámetros?
Al ser un ajuste no lineal en los parámetros, la solución que se obtiene minimizando cuadrados mínimos no es única. Por eso, conviene ayudar al algoritmo de minimización dando parámetros iniciales para iniciar la búsqueda. En este caso, el único parámetro realmente sensible es \omega.
Código: generación de datos de ejemplo
import numpy as np
import matplotlib.pyplot as plt
import labo1
def func(t, A, w, phi, B):
return A * np.cos(w * t + phi) + B
t = np.linspace(0, 3, 100, endpoint=False)
v_real = func(t, A=1, w=10, phi=0, B=0)
sigma_v = 0.2
v = np.random.default_rng(0).normal(v_real, sigma_v)Código: ajuste por cuadrados mínimos
# Ajuste cambiando los parámetros iniciales.
# Por defecto, usa 1.0 para todos.
r_1 = labo1.curve_fit(func, x=t, y=v, y_err=sigma_v)
r_2 = labo1.curve_fit(func, x=t, y=v, y_err=sigma_v, initial_params={"w": 9})Código: generación del gráfico
def label(inicial, ajustado):
x, sigma = ajustado
return rf"$w = {inicial} \;\mapsto\; {x:+.2f} \pm {sigma:.2f}$"
plt.figure(figsize=(4, 2))
plt.errorbar(r_1.x, r_1.y, r_1.y_err, fmt=".")
plt.plot(t, r_1.eval(t), linewidth=2, label=label(inicial=1, ajustado=r_1["w"]))
(plt.plot(t, r_2.eval(t), linewidth=2, label=label(inicial=9, ajustado=r_2["w"])))
plt.xlabel("Tiempo (s)")
plt.ylabel("Voltaje (V)")
plt.legend(title=r"Parámetro (inicial $\mapsto$ final)", bbox_to_anchor=(1, 1))
Para estimar el error en los parámetros, se necesita conocer el error de la variable del eje y, es decir, el error en el voltaje.
- ¿Cómo puede estimarse el error en el voltaje?
4.2.2 Opción 1 (bis): oscilador amortiguado
Este modelo suele ser más sencillo de ajustar, ya que es menos sensible a la condición inicial para \omega. Por eso, resulta útil incluso si el amortiguamiento no es apreciable.
Ver extra
La fuerza de rozamiento con el aire puede modelarse como una fuerza viscosa, proporcional a la velocidad: F = b \cdot v. En ese caso, la posición en función del tiempo es x(t) = d \, e^{-\gamma t} \, \cos(\omega t + \phi) + x_{eq} donde
- \omega^2 = \omega_0^2 - \gamma^2,
- \gamma = \frac{b}{2 \sqrt{m k}},
- \omega_0^2 = \frac{k}{m}.
4.2.3 Opción 2 (extra): tiempo entre máximos
Ver extra
Una forma alternativa es calcular el periodo como la diferencia de tiempo entre dos máximos máximos de la señal (Figura 4, naranja). También se pueden elegir dos mínimos (verde), o cada vez que vuelva a pasar por la misma altura en la misma dirección (rojo).
Código
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal
def func(t):
return np.cos(4 * t - 0.5)
t = np.linspace(0, 3, 21, endpoint=False)
v = func(t)
def plot_bar(ix, *, dy, color):
x = t[ix]
y = np.mean(v[ix])
plt.scatter(t[ix], v[ix], color=color)
plt.errorbar(
x=np.mean(x),
xerr=np.diff(x) / 2,
y=y + dy,
capsize=5,
color=color,
)
plt.figure(figsize=(6, 2))
plt.xlabel("Tiempo (s)")
plt.ylabel("Voltage (V)")
plt.scatter(t, v)
ix, _ = scipy.signal.find_peaks(v)
plot_bar(ix, dy=0.3, color="C1")
ix, _ = scipy.signal.find_peaks(-v)
plot_bar(ix, dy=-0.3, color="C2")
ix, = np.where(np.diff(np.sign(v), prepend=np.nan) < 0)
plot_bar(ix, dy=0.4, color="C3")
Es más simple asignar un error a este método cuando la señal tiene poco ruido en el eje y, como en las primeras dos señales de la Figura 3. En el caso de la última señal, el ruido en el voltaje añade un error al elegir el tiempo correspondiente al máximo “real”.
- Para las dos primeras señales de la Figura 3, ¿qué error asignaría para la medición de 1 período con este método? ¿Es el mismo en ambos casos? Recuerde que el error en el tiempo de cada muestra (punto) es despreciable.
4.3 Período en función de la masa
Una vez determinado el período \tau para una masa m, se puede repetir el procedimiento para varias masas y ajustar la relación entre ambas magnitudes:
m = k \cdot \left( \frac{\tau}{2\pi} \right)^2 \tag{8}
que se obtiene al reescribir la ecuación Ecuación 4 en términos del período \tau = \frac{2\pi}{\omega}.
¿Este modelo ajusta bien los datos o hace falta agregar un término constante? En ese caso, ¿qué representa? Cushing (1984).
Si fuera necesario agregar un término constante a la ecuación Ecuación 8, ¿qué habría sucedido si hubieran determinado k a partir de una sola medición (m, \tau)?
Código para analizar múltiples archivos
Para cada masa, se tendrá un archivo con la medición de la fuerza en función del tiempo. Por ejemplo, para una masa de (100 \pm 1)\,\text{g}, se guardó un archivo llamado 100.txt. Esta información puede organizarse en una tabla (Tabla 1), donde la columna “archivo” contiene el nombre del archivo.
| archivo | masa | masa_err | |
|---|---|---|---|
| 0 | 100.txt | 100 | 1 |
| 1 | 200.txt | 200 | 1 |
| 2 | 300.txt | 300 | 1 |
Si el análisis de cada archivo es independiente de los demás, puede escribirse una función que reciba el nombre del archivo y devuelva el resultado del análisis para ese archivo en particular. Luego, esa función puede aplicarse a cada fila de la columna archivo:
import pandas as pd
def cargar(archivo: str):
"""Carga las mediciones del SensorDAQ."""
df = pd.read_csv(archivo)
return df
def ajustar(df):
"""Ajusta las mediciones del voltaje en función del tiempo."""
# Completar
result = labo1.curve_fit(..., x=df["..."])
return result
def extraer_periodo(result):
"""Extrae el período y su error del resultado del ajuste."""
T, sigma_T = result["T"]
return {
"periodo": T,
"sigma_periodo": sigma_T,
}
def analisis(archivo: str):
df = cargar(archivo)
result = ajustar(df)
return extraer_periodo(result)
df = pd.read_csv(...) # Carga la tabla
df_periodo = (
df["archivo"] # Elije la columna archivo
.map(analisis) # Ejecuta la función en cada fila
.apply(pd.Series) # Expande la columna a tabla
)
df = df.join(df_periodo) # Une la tabla original con la nueva
dfSi necesitan acceder a las demás columnas para hacer el ajuste, por ejemplo, para estimar una frecuencia inicial a partir de la masa, pueden usar:
def func(fila):
archivo = fila["archivo"]
masa = fila["masa"]
w_inicial = k * masa
result = labo1.curve_fit(..., initial_params={"w": w_inicial})
return result
df.apply(func, axis="columns")