ACTIVITE 2 – Méthode d’Euler : 2ᵉ exemple, chute avec frottements quadratiques

Chargement de Python... Veuillez patienter.

Après l'exemple simple de la résolution du circuit RC série dans l'activité précédente, intéressons-nous maintenant à un problème physique décrit par une équation différentielle qui n'est pas linéaire. La solution analytique est alors plus difficile à obtenir (et parfois, c'est même impossible !), la résolution numérique a alors un réel intérêt.

On étudie une goutte de pluie de masse \(m\) qui chute verticalement dans le champ de pesanteur. L'air exerce sur cette goutte des frottements fluides proportionnels au carré de la vitesse : \( \overrightarrow{f}=-\alpha v^2 \overrightarrow{u_x} \).

Quelle est l'équation différentielle vérifiée par la vitesse \(v(t)\) de la goutte ?

Il s'agit d'une équation non linéaire, sa résolution analytique est difficile... Notre résolution numérique approchée a donc un intérêt. Néanmoins, sans résoudre, on peut tout de même déterminer très facilement la vitesse limite atteinte par la goutte en régime permanent.

Que vaut la vitesse limite atteinte par la goutte \(v(\infty)\) en fonction de \(m\), \(g\) et \(\alpha\) ?
v(\infty)=

A vous de jouer : vous allez écrire un programme Python utilisant la méthode d'Euler pour résoudre cette équation différentielle et connaître la vitesse de la goutte pendant le régime transitoire.

On prendra une assez grosse goutte de pluie supposée sphérique de rayon \(R=4\text{ mm}\).
Sa masse est donc \( m = \rho\times\frac{4}{3}\pi R^3 = 2,7\cdot 10^{-4}\text{ kg} \).
Le coefficient de frottement vaut \( \alpha = \frac{1}{2}\rho\pi R^2 C_x = \frac{1}{2}\times 1000 \times 3,14 \times \left(0,004\right)^2 \times 0,5 = 1,3 \cdot 10^{-2}\text{ N.s}^2\text{.m}^{-2} \)

Ecrire le programme Python permettant de calculer la vitesse à différents instants en utilisant la méthode d'Euler, en vous inspirant de l'activité précédente.
On appellera g la pesanteur terrestre, alpha le coefficient de frottement, v le tableau numpy des vitesses, et a celui des accélérations.
Pour l'intervalle total de temps tf sur lequel résoudre, vous pourrez estimer au doigt mouillé (avec votre sens physique) la durée au bout de laquelle une goutte de pluie initialement immobile atteint sa vitesse de chute limite. Puis, après avoir tracé le graphe, vous pourrez réajuster cette valeur.

Afficher le graphe de la vitesse \(v(t)\) calculée en fonction du temps :

Commenter. (La vitesse limite est elle correcte ? Qu'avez-vous constaté pour certains paramétrages de la discrétisation ?...)

Remarque : En fait, cette équation différentielle peut-être résolue analytiquement de manière exacte. La solution, relativement difficile à trouver par le calcul, est : \[ v(t) = \sqrt{\frac{mg}{\alpha}} \tanh \left(\frac{gt}{\sqrt{\frac{mg}{\alpha}}}\right) \]

terminal = false packages = ["numpy", "matplotlib"] [[fetch]] files = [] import sys import io import traceback from js import document, console, window from pyodide.ffi import create_proxy import base64 # Configuration matplotlib pour PyScript import matplotlib matplotlib.use('Agg') # Backend non-interactif pour PyScript import matplotlib.pyplot as plt # Contexte global partagé entre toutes les cellules global_context = {'plt': plt, 'matplotlib': matplotlib} def capture_output(func, *args, **kwargs): """Capture la sortie d'une fonction""" old_stdout = sys.stdout old_stderr = sys.stderr stdout_capture = io.StringIO() stderr_capture = io.StringIO() sys.stdout = stdout_capture sys.stderr = stderr_capture try: result = func(*args, **kwargs) stdout_output = stdout_capture.getvalue() stderr_output = stderr_capture.getvalue() return result, stdout_output, stderr_output finally: sys.stdout = old_stdout sys.stderr = old_stderr def handle_matplotlib_figures(output_element): """Gère l'affichage des figures matplotlib""" current_figures = plt.get_fignums() if current_figures: # Créer ou récupérer le conteneur des figures figures_container = output_element.querySelector('.figures-container') if not figures_container: figures_container = document.createElement('div') figures_container.className = 'figures-container' figures_container.style.cssText = ''' display: flex; flex-wrap: wrap; gap: 15px; margin: 10px 0; justify-content: center; ''' output_element.appendChild(figures_container) for fig_num in current_figures: fig = plt.figure(fig_num) # Convertir la figure en image base64 buf = io.BytesIO() fig.savefig(buf, format='png', bbox_inches='tight', dpi=150) buf.seek(0) img_data = base64.b64encode(buf.read()).decode() buf.close() # Créer un conteneur pour la figure avec bouton de suppression figure_wrapper = document.createElement('div') figure_wrapper.className = 'figure-wrapper' figure_wrapper.style.cssText = ''' position: relative; display: inline-block; border: 1px solid #ddd; border-radius: 8px; padding: 10px; background: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin: 5px; ''' # Créer l'élément img pour afficher la figure img_element = document.createElement('img') img_element.src = f'data:image/png;base64,{img_data}' img_element.className = 'matplotlib-figure' img_element.style.cssText = ''' max-width: 300px; max-height: 250px; height: auto; display: block; border-radius: 4px; ''' # Créer le bouton de suppression delete_btn = document.createElement('button') delete_btn.innerHTML = '×' delete_btn.className = 'figure-delete-btn' delete_btn.style.cssText = ''' position: absolute; top: 5px; right: 5px; width: 25px; height: 25px; border: none; border-radius: 50%; background: rgba(220, 53, 69, 0.8); color: white; font-size: 16px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1; z-index: 10; ''' delete_btn.title = 'Supprimer cette figure' # Ajouter l'événement de suppression def create_delete_handler(wrapper): def delete_figure(event): wrapper.remove() return delete_figure delete_btn.onclick = create_delete_handler(figure_wrapper) # Assembler la figure figure_wrapper.appendChild(img_element) figure_wrapper.appendChild(delete_btn) figures_container.appendChild(figure_wrapper) # Fermer toutes les figures pour éviter les accumulations plt.close('all') return True return False def execute_python_code(code, cell_id): """Exécute le code Python et affiche le résultat""" output_element = document.querySelector(f'#output-{cell_id}') loading_element = document.querySelector(f'#loading-{cell_id}') # Afficher le loading loading_element.style.display = 'block' # Vérifier s'il y a déjà des figures figures_container = output_element.querySelector('.figures-container') if figures_container: # S'il y a des figures, effacer seulement le contenu texte text_outputs = output_element.querySelectorAll('.text-output') for text_output in text_outputs: text_output.remove() # Réinitialiser le style de base sans effacer les figures output_element.className = 'output' # Vider seulement le texte direct, pas les éléments enfants nodes_to_remove = [] for node in output_element.childNodes: if node.nodeType == 3: # Text node nodes_to_remove.append(node) for node in nodes_to_remove: node.remove() else: # Pas de figures, effacer tout output_element.innerHTML = '' output_element.className = 'output' try: def run_code(): # Exécuter le code dans le contexte global exec(code, global_context) # Si la dernière ligne est une expression (pas un statement), l'évaluer et retourner le résultat lines = code.strip().split('\n') if lines: last_line = lines[-1].strip() # Vérifier que ce n'est pas un statement Python commun if (last_line and not last_line.startswith((' ', '\t')) and not last_line.endswith(':') and not last_line.startswith(('print(', 'import ', 'from ', 'def ', 'class ', 'if ', 'for ', 'while ', 'try:', 'except', 'with ', 'assert ', 'del ', 'pass', 'break', 'continue', 'return', 'yield', 'raise', 'global ', 'nonlocal ', 'plt.', 'fig')) and '=' not in last_line.split('#')[0]): # Éviter les assignments try: return eval(last_line, global_context) except: pass return None result, stdout_output, stderr_output = capture_output(run_code) # Gérer l'affichage des figures matplotlib (s'ajoute aux existantes) has_figures = handle_matplotlib_figures(output_element) # Construire la sortie texte output = "" if stdout_output: output += stdout_output if stderr_output: output += stderr_output if result is not None: output += str(result) if output: # S'il y a déjà des figures, ajouter le texte à la suite if has_figures: text_div = document.createElement('div') text_div.className = 'text-output' text_div.textContent = output output_element.appendChild(text_div) else: output_element.textContent = output elif not has_figures: output_element.textContent = "✓ Code exécuté avec succès" output_element.className = 'output success' except Exception as e: error_output = f"❌ Erreur:\n{str(e)}\n\n{traceback.format_exc()}" output_element.textContent = error_output output_element.className = 'output error' finally: loading_element.style.display = 'none' # Exposer les fonctions à JavaScript window.execute_python_code = create_proxy(execute_python_code) # Signaler que PyScript est prêt console.log("PyScript ready!") try: # Activer l'interface immédiatement status_element = document.querySelector('#pyscript-status') if status_element: status_element.innerHTML = 'Python est prêt ! Vous pouvez maintenant exécuter votre code.' status_element.className = 'pyscript-status pyscript-ready' # Activer tous les boutons d'exécution run_buttons = document.querySelectorAll('.btn-run') for button in run_buttons: button.disabled = False # Notifier JavaScript window.pyScriptIsReady = True except Exception as e: console.log(f"Error during PyScript initialization: {e}")