=========================================================== Ejercicios: Integración y Aplicación de Conceptos en Python para Física =========================================================== Introducción ----------- En esta serie de ejercicios, integraremos conceptos fundamentales de programación en Python para resolver problemas de física. Trabajaremos con funciones que manipulan listas, módulos estándar y propios, implementaciones recursivas, y aplicaciones a problemas físicos realistas. Estos ejercicios están diseñados para fortalecer tus habilidades de programación y tu capacidad de modelar fenómenos físicos mediante código. Parte I: Funciones que Operan sobre Listas ------------------------------------------ Ejercicio 1: Análisis de Datos de Movimiento ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Escribe una función ``analizar_trayectoria`` que reciba una lista de tuplas donde cada tupla contiene la posición (x, y) de un objeto en movimiento en diferentes instantes de tiempo, igualmente espaciados. La función debe tener un parámetro adicional ``intervalo_tiempo`` que indique el intervalo de tiempo en segundos entre cada medición (por defecto 1 segundo). La función debe calcular y retornar: 1. La distancia total recorrida. 2. El desplazamiento neto (distancia en línea recta desde el punto inicial al final). 3. La velocidad media (magnitud del desplazamiento dividido por el tiempo total). 4. Una lista con las velocidades instantáneas entre puntos consecutivos. 5. Los puntos donde la velocidad instantánea cambia de dirección (si existen). La distancia entre dos puntos consecutivos se calcula como: .. math:: d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} Ejemplo de uso: .. code-block:: python trayectoria = [(0, 0), (1, 1), (2, 0), (3, 2), (4, 1)] # Usando el valor por defecto (intervalo de 1 segundo) resultados = analizar_trayectoria(trayectoria) print(resultados) # Especificando un intervalo diferente (0.5 segundos entre mediciones) resultados_detallados = analizar_trayectoria(trayectoria, intervalo_tiempo=0.5) print(resultados_detallados) # Debería mostrar la distancia total, desplazamiento, velocidad media, # lista de velocidades instantáneas y puntos de cambio de dirección Ejercicio 2: Procesamiento de Datos Experimentales ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implementa una función ``filtrar_outliers`` que tome una lista de mediciones experimentales y elimine valores atípicos (outliers). La función debe utilizar el método de la desviación absoluta de la mediana (MAD): .. math:: \text{MAD} = \text{mediana}(|x_i - \text{mediana}(X)|) Cualquier valor que se desvíe más de `n` veces el MAD de la mediana se considera un outlier, donde `n` es un parámetro ajustable (típicamente entre 2 y 3). La función debe retornar una nueva lista sin los outliers y un informe en forma de diccionario que incluya: - ``'num_outliers'``: Número de outliers eliminados - ``'outliers'``: Lista con los valores de los outliers - ``'media_antes'``: Media aritmética antes de la filtración - ``'desviacion_antes'``: Desviación estándar antes de la filtración - ``'media_despues'``: Media aritmética después de la filtración - ``'desviacion_despues'``: Desviación estándar después de la filtración Ejemplo: .. code-block:: python mediciones = [10.1, 10.2, 10.1, 10.3, 10.2, 15.6, 10.0, 10.1, -2.5, 10.3] # 15.6 y -2.5 son outliers datos_filtrados, informe = filtrar_outliers(mediciones, n=2.5) print(datos_filtrados) # Lista sin outliers # El informe es un diccionario con la información del proceso print(informe['num_outliers']) # Debería mostrar 2 print(informe['outliers']) # Debería mostrar [15.6, -2.5] print(informe['media_antes']) # Media antes de filtrar print(informe['desviacion_antes']) # Desviación estándar antes de filtrar print(informe['media_despues']) # Media después de filtrar print(informe['desviacion_despues']) # Desviación estándar después de filtrar Ejercicio 3: Búsqueda Binaria para Valores de Energía ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implementa una función ``busqueda_binaria`` que encuentre la posición de un valor en una lista ordenada de energías (en electronvoltios, eV). Si el valor exacto no existe, la función debe retornar la posición donde debería insertarse para mantener el orden. La función debe incluir un parámetro opcional para especificar una tolerancia, permitiendo encontrar valores dentro de un rango cercano al solicitado. **Nota sobre búsqueda binaria:** La búsqueda binaria es un algoritmo eficiente para encontrar un elemento en una lista ordenada. Funciona dividiendo repetidamente a la mitad el segmento de la lista que podría contener el elemento, hasta reducir las ubicaciones posibles a solo una. A diferencia de la búsqueda lineal (que revisa cada elemento), la búsqueda binaria tiene una complejidad de O(log n), haciéndola mucho más rápida para listas grandes. Ejemplo: .. code-block:: python energias = [1.2, 2.4, 3.1, 4.5, 5.6, 6.7, 8.2, 9.5] posicion = busqueda_binaria(energias, 4.5) # Debería retornar 3 posicion_aprox = busqueda_binaria(energias, 4.6, tolerancia=0.2) # Debería retornar 3 # Ejemplo donde el valor no existe y se retorna la posición donde debería insertarse posicion_insercion = busqueda_binaria(energias, 7.0) # Debería retornar 6 print(f"El valor 7.0 debería insertarse en la posición {posicion_insercion}") energias_nueva = energias.copy() energias_nueva.insert(posicion_insercion, 7.0) print(f"Lista actualizada: {energias_nueva}") # [1.2, 2.4, 3.1, 4.5, 5.6, 6.7, 7.0, 8.2, 9.5] # Ejercicio 4: Algoritmo de Ordenamiento para Partículas Ejercicio 4: Algoritmo de Ordenamiento para Partículas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Escribe una función ``ordenar_particulas`` que ordene una lista de partículas representadas como diccionarios. Cada partícula tiene propiedades como masa, carga y energía. La función debe permitir especificar la propiedad por la cual ordenar y el orden (ascendente o descendente). Implementa el algoritmo de ordenamiento por selección (selection sort) desde cero, sin usar las funciones de ordenamiento integradas de Python. Ejemplo: .. code-block:: python particulas = [ {'nombre': 'Electrón', 'masa': 9.1e-31, 'carga': -1.6e-19, 'energia': 5.2}, {'nombre': 'Protón', 'masa': 1.67e-27, 'carga': 1.6e-19, 'energia': 7.8}, {'nombre': 'Neutrón', 'masa': 1.67e-27, 'carga': 0, 'energia': 2.1}, {'nombre': 'Muón', 'masa': 1.88e-28, 'carga': -1.6e-19, 'energia': 12.3} ] # Ordenar por masa (ascendente) ordenadas_por_masa = ordenar_particulas(particulas, 'masa') # Ordenar por energía (descendente) ordenadas_por_energia = ordenar_particulas(particulas, 'energia', ascendente=False) El algoritmo de ordenamiento por selección (Selection Sort) es un método simple que opera directamente sobre la lista original, realizando un ordenamiento "in-place" (en el lugar): 1. Se trabaja con una única lista, donde en cada momento hay una parte ordenada (al inicio) y una parte no ordenada (al final). 2. Inicialmente, toda la lista se considera no ordenada. 3. En cada iteración: - Se recorre la porción no ordenada para encontrar el elemento más pequeño. - Este elemento mínimo se intercambia con el primer elemento de la porción no ordenada. - La frontera entre la porción ordenada y no ordenada avanza una posición. 4. El proceso continúa hasta que toda la lista queda ordenada. A continuación se muestra un diagrama que ilustra el funcionamiento del algoritmo de ordenamiento por selección con una lista de 5 números: .. graphviz:: :caption: Ordenamiento por selección de una lista de 5 números. digraph selection_sort { // Configuración general graph [rankdir=TB, nodesep=0.3, ranksep=0.3]; node [shape=plaintext, fontname="Helvetica", fontsize=11]; edge [fontname="Helvetica", fontsize=9]; // Estado inicial initial [label=<
Estado Inicial
83517
>]; // Iteración 1 it1 [label=<
Iteración 1: Buscar mínimo
83517
Mínimo encontrado: 1
>]; it1_result [label=<
Después del intercambio
13587
OrdenadoDesordenado
>]; // Iteración 2 it2 [label=<
Iteración 2: Buscar mínimo
13587
OrdenadoMínimo: 3
>]; it2_result [label=<
Después del intercambio
13587
OrdenadoDesordenado
>]; // Iteración 3 it3 [label=<
Iteración 3: Buscar mínimo
13587
OrdenadoMínimo: 5
>]; it3_result [label=<
Después del intercambio
13587
OrdenadoDesordenado
>]; // Iteración 4 it4 [label=<
Iteración 4: Buscar mínimo
13587
OrdenadoMínimo: 7
>]; it4_result [label=<
Después del intercambio
13578
OrdenadoDesordenado
>]; // Resultado final final [label=<
Lista Ordenada
13578
>]; // Conexiones initial -> it1 -> it1_result -> it2 -> it2_result -> it3 -> it3_result -> it4 -> it4_result -> final; } Parte II: Módulos y Organización de Código ------------------------------------------ Ejercicio 5: Creación de un Módulo de Mecánica ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Crea un módulo llamado ``mecanica.py`` que contenga funciones para cálculos relacionados con mecánica clásica: 1. ``calcular_trabajo(fuerza, desplazamiento, angulo=0)``: Calcula el trabajo realizado por una fuerza constante sobre un objeto. .. math:: W = F \cdot d \cdot \cos(\theta) 2. ``energia_cinetica(masa, velocidad)``: Calcula la energía cinética de un objeto. .. math:: E_k = \frac{1}{2}mv^2 3. ``energia_potencial_gravitatoria(masa, altura, g=9.8)``: Calcula la energía potencial gravitatoria. .. math:: E_p = mgh 4. ``conservacion_energia(energia_inicial, energia_final, tolerancia=1e-6)``: Verifica si se conserva la energía dentro de una tolerancia. Luego, crea un script principal ``simulacion_energia.py`` que importe y use estas funciones para simular un péndulo simple, mostrando cómo las energías cinética y potencial varían durante la oscilación y verificando la conservación de la energía total. El script debe: - Simular el movimiento del péndulo para varios periodos - Calcular y las energías cinética, potencial y total a lo largo del tiempo - Verificar que la energía total se conserva Ejercicio 6: Análisis Físico con Módulos Estándar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Escribe un programa `pendulo.py` que analice datos de oscilaciones de un péndulo utilizando módulos estándar de Python como `math`, `random`, `statistics` y `sys`. El programa debe: 1. **Generar datos sintéticos de un péndulo**: - Crear una función `simular_pendulo(longitud, tiempo_max, dt, ruido)` que: - Simule la posición angular θ(t) = θ₀·cos(ωt) donde ω = √(g/L) - Añada ruido aleatorio a las posiciones - Devuelva dos listas: tiempos y posiciones angulares 2. **Determinar el periodo experimental**: - Implementar una función `calcular_periodo(tiempos, posiciones)` que: - Detecte los cruces por cero con pendiente positiva en la señal - Calcule el tiempo entre cruces consecutivos - Devuelva la lista de periodos medidos 3. **Analizar los resultados**: - Calcular estadísticas básicas del periodo (media, mediana, desviación estándar) - Estimar el valor de g usando la fórmula T = 2π√(L/g) - Mostrar los resultados en un formato legible El programa debe funcionar con valores preestablecidos (sin necesidad de argumentos de línea de comando) pero permitir modificarlos fácilmente en el código. **Ejemplo de código base para empezar**: .. code-block:: python import math import random import statistics def simular_pendulo(longitud=1.0, amplitud=0.1, tiempo_max=10.0, dt=0.01, ruido=0.005): """ Simula el movimiento de un péndulo y devuelve los datos con ruido. Args: longitud: Longitud del péndulo en metros amplitud: Amplitud inicial en radianes tiempo_max: Tiempo máximo de simulación en segundos dt: Paso temporal en segundos ruido: Magnitud del ruido aleatorio en radianes Returns: Tupla con (lista_tiempos, lista_posiciones) """ # Tu código aquí: implementar la simulación pass def calcular_periodo(tiempos, posiciones): """ Calcula los periodos observados a partir de los datos de posición. Args: tiempos: Lista de tiempos posiciones: Lista de posiciones angulares Returns: Lista con los periodos medidos """ # Tu código aquí: implementar detección de periodos pass def main(): # Parámetros de simulación longitud = 1.0 # metros amplitud = 0.1 # radianes g_teorico = 9.8 # m/s² # Simular el péndulo tiempos, posiciones = simular_pendulo(longitud=longitud, amplitud=amplitud) # Calcular periodos periodos = calcular_periodo(tiempos, posiciones) # Analizar resultados periodo_medio = statistics.mean(periodos) periodo_std = statistics.stdev(periodos) periodo_mediana = statistics.median(periodos) # Calcular g experimental # Tu código aquí # Imprimir resultados print("=== ANÁLISIS DE PÉNDULO SIMPLE ===") print(f"Longitud: {longitud} m") print(f"Periodo medio: {periodo_medio:.4f} ± {periodo_std:.4f} s") print(f"Valor de g calculado: {g_calculado:.3f} ± {g_incertidumbre:.3f} m/s²") if __name__ == "__main__": main() Parte III: Recursividad ----------------------- Ejercicio 7: Integración Numérica Recursiva ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implementa una función recursiva ``integrar_recursiva`` que calcule la integral definida de una función utilizando el método de Simpson recursivo adaptativo. El método de Simpson recursivo divide el intervalo en dos mitades y aplica la regla: .. math:: \int_{a}^{b} f(x) dx \approx \frac{b-a}{6} \left[ f(a) + 4f\left(\frac{a+b}{2}\right) + f(b) \right] Para estimar el error, se debe: 1. Calcular la integral con la fórmula de Simpson para el intervalo completo [a,b], llamémosla S(a,b) 2. Dividir el intervalo en dos mitades [a,m] y [m,b] donde m=(a+b)/2 3. Calcular la integral para cada mitad: S(a,m) y S(m,b) 4. Comparar S(a,b) con la suma S(a,m) + S(m,b) 5. Estimar el error como: .. math:: error = \left| S(a,b) - [S(a,m) + S(m,b)] \right| La recursividad se aplica si el error estimado es mayor que una tolerancia especificada. En ese caso, se divide el intervalo en dos y se aplica la integración a cada mitad, sumando los resultados. Si el error es menor que la tolerancia, se acepta el valor S(a,m) + S(m,b) como aproximación válida. Tu implementación debe: - Aceptar la función a integrar, los límites de integración y la tolerancia - Aplicar recursivamente el método de Simpson - Utilizar la estimación de error como criterio de parada - Opcionalmente, incluir un parámetro para limitar la profundidad máxima de recursión Utiliza esta función para calcular: 1. La integral de la función seno entre 0 y π (cuyo resultado exacto es 2) 2. La integral de la función :math:`e^{-x^2}` entre -1 y 1 (relacionada con la función error) Ejemplo: .. code-block:: python import math def integrar_recursiva(f, a, b, tolerancia=1e-6, profundidad_max=30): # Implementa el método de Simpson recursivo con estimación de error # ... # Calcular la integral de sin(x) de 0 a π resultado = integrar_recursiva(math.sin, 0, math.pi, tolerancia=1e-6) print(f"Integral de sin(x) de 0 a π: {resultado}") # Debería ser cercano a 2 # Calcular la integral de e^(-x²) de -1 a 1 def gaussiana(x): return math.exp(-x**2) resultado_gaussiana = integrar_recursiva(gaussiana, -1, 1, tolerancia=1e-8) print(f"Integral de e^(-x²) de -1 a 1: {resultado_gaussiana}") Parte IV: Problemas de Aplicación --------------------------------- Ejercicio 8: Difusión de Calor en una Varilla ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implementa una simulación numérica de la ecuación de difusión de calor en una dimensión: .. math:: \frac{\partial T}{\partial t} = \alpha \frac{\partial^2 T}{\partial x^2} donde :math:`T` es la temperatura, :math:`t` es el tiempo, :math:`x` es la posición, y :math:`\alpha` es la difusividad térmica. Para resolver numéricamente esta ecuación, usaremos el método de diferencias finitas: La ecuación de actualización es: .. math:: T_i^{n+1} = T_i^n + \alpha \frac{\Delta t}{(\Delta x)^2} (T_{i+1}^n - 2T_i^n + T_{i-1}^n) Donde: - :math:`i` representa el índice de la posición espacial en la varilla (0, 1, 2, ..., N) - :math:`n` representa el índice del paso temporal (0, 1, 2, ..., M) - :math:`T_i^n` es la temperatura en la posición :math:`i` y en el tiempo :math:`n` **Condiciones del problema:** - **Condición inicial:** Temperatura de 100°C en el centro de la varilla y 25°C en el resto - **Condiciones de contorno:** Los extremos de la varilla se mantienen fijos a 25°C - **Difusividad térmica:** :math:`\alpha = 0.01` - **Longitud de la varilla:** 1 metro - **Tiempo de simulación:** 1 segundo Tu programa debe: 1. Crear una malla espacial para la varilla (representada por una lista de temperaturas) 2. Inicializar la temperatura según la condición inicial dada 3. Para cada paso temporal: a. Calcular la distribución de temperatura en el tiempo n+1 a partir de la distribución actual (tiempo n) b. Actualizar la distribución actual con los nuevos valores calculados c. Repetir el proceso para el número de pasos temporales deseado 4. Mostrar la distribución de temperatura para diferentes instantes de tiempo durante la simulación .. list-table:: Evolución de la Temperatura en la Varilla :header-rows: 1 :widths: 15 10 10 10 10 10 25 :class: heat-diffusion-table * - Tiempo - Posición x₀ - Posición x₁ - Posición x₂ - Posición x₃ - Posición x₄ - Descripción * - t₀ (inicial) - 25°C - 25°C - **100°C** - 25°C - 25°C - Condición inicial: calor en el centro * - t₁ (ciclo 1) - 25°C - 26.2°C - 97.6°C - 26.2°C - 25°C - El calor comienza a difundirse * - t₂ (ciclo 2) - 25°C - 27.3°C - 95.3°C - 27.3°C - 25°C - La temperatura continúa equilibrándose .. note:: - Los extremos (x₀ y x₄) permanecen a temperatura constante de 25°C (condiciones de borde). - Para estos cálculos, se usó α = 0.01, Δx = 0.25 m y Δt = 0.1 s. **Fórmula de actualización:** .. math:: T_i^{n+1} = T_i^n + \alpha \frac{\Delta t}{(\Delta x)^2} (T_{i+1}^n - 2T_i^n + T_{i-1}^n) **Ejemplo de cálculo para el primer ciclo:** Para calcular la temperatura en la posición x₂ en el tiempo t₁: .. math:: T_2^1 &= T_2^0 + \alpha \frac{\Delta t}{(\Delta x)^2} (T_{3}^0 - 2T_2^0 + T_{1}^0) \\ &= 100°C + 0.01 \cdot \frac{0.1}{(0.25)^2} \cdot (25°C - 2 \cdot 100°C + 25°C) \\ &= 100°C + 0.016 \cdot (-150°C) \\ &= 100°C - 2.4°C \\ &= 97.6°C En cada ciclo, todas las temperaturas interiores (excepto las de los extremos) se actualizan simultáneamente según esta fórmula. Ejercicio 9: Oscilador Armónico Amortiguado ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implementa una simulación de un oscilador armónico amortiguado, como un sistema masa-resorte con fricción. La ecuación diferencial que gobierna el sistema es: .. math:: m\frac{d^2x}{dt^2} + b\frac{dx}{dt} + kx = F(t) donde :math:`m` es la masa, :math:`b` es el coeficiente de amortiguamiento, :math:`k` es la constante del resorte, y :math:`F(t)` es una fuerza externa que puede variar con el tiempo. Para resolver numéricamente esta ecuación, sigue estos pasos: 1. **Transformación a ecuaciones de primer orden**: Introduce las variables :math:`x` (posición) y :math:`v` (velocidad), para convertir la ecuación de segundo orden en un sistema de dos ecuaciones de primer orden: .. math:: \frac{dx}{dt} &= v \\ \frac{dv}{dt} &= \frac{F(t) - bv - kx}{m} 2. **Método de integración numérica**: Utiliza el método de Euler simple para avanzar la solución en pasos de tiempo pequeños: Para cada paso de tiempo de tamaño :math:`\Delta t`: .. math:: x_{n+1} &= x_n + v_n \cdot \Delta t \\ v_{n+1} &= v_n + \frac{F(t_n) - bv_n - kx_n}{m} \cdot \Delta t 3. **Fuerza externa**: Implementa al menos dos tipos diferentes de fuerza externa: - Fuerza constante: :math:`F(t) = F_0` - Fuerza sinusoidal: :math:`F(t) = F_0 \sin(\omega t)` 4. **Visualización**: Calcula y grafica: - La posición :math:`x(t)` en función del tiempo - La velocidad :math:`v(t)` en función del tiempo - La energía total: :math:`E(t) = \frac{1}{2}mv^2 + \frac{1}{2}kx^2` 5. **Análisis de regímenes**: Experimenta con diferentes valores de los parámetros para identificar: - Régimen subamortiguado (oscilatorio): :math:`b^2 < 4mk` - Régimen críticamente amortiguado: :math:`b^2 = 4mk` - Régimen sobreamortiguado: :math:`b^2 > 4mk` Para cada régimen, muestra un gráfico representativo y explica las diferencias observadas. Sugerencias prácticas: - Utiliza un paso de tiempo pequeño (p. ej., :math:`\Delta t = 0.01` segundos) para mantener la estabilidad numérica - Simula el sistema durante un tiempo suficientemente largo para observar el comportamiento completo - Puedes elegir valores iniciales como :math:`x(0) = 1` (posición desplazada) y :math:`v(0) = 0` (velocidad inicial nula) Conclusión --------- Estos ejercicios están diseñados para integrar múltiples conceptos de programación en Python aplicados a problemas físicos relevantes. Abarcan desde manipulación básica de datos hasta simulaciones complejas, promoviendo un enfoque organizado y modular en la escritura de código. Recuerda que la clave para resolver estos problemas no es solo obtener el resultado correcto, sino desarrollar soluciones que sean: - Claras y bien documentadas - Eficientes en términos de recursos computacionales - Modeladas correctamente según los principios físicos - Organizadas de manera modular y reutilizable Al trabajar en estos ejercicios, desarrollarás habilidades valiosas para la modelación computacional en física y otras ciencias.