Buckets:
| import json | |
| import csv | |
| import io | |
| from datetime import datetime | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| from django.db import models | |
| from django.conf import settings | |
| class SimulationMethod(models.Model): | |
| name = models.CharField(max_length=200) | |
| slug = models.SlugField(unique=True) | |
| description = models.TextField() | |
| theory = models.TextField(blank=True) | |
| parameters_schema = models.JSONField(default=dict) | |
| default_parameters = models.JSONField(default=dict) | |
| is_active = models.BooleanField(default=True) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| class Meta: | |
| ordering = ['name'] | |
| def __str__(self): | |
| return self.name | |
| class SimulationRun(models.Model): | |
| STATUS_CHOICES = [ | |
| ('PENDING', 'En attente'), | |
| ('RUNNING', 'En cours'), | |
| ('SUCCESS', 'Terminé'), | |
| ('FAILURE', 'Échoué'), | |
| ('CANCELLED', 'Annulé'), | |
| ] | |
| method = models.ForeignKey( | |
| SimulationMethod, | |
| on_delete=models.CASCADE, | |
| related_name='runs' | |
| ) | |
| name = models.CharField(max_length=200, blank=True) | |
| parameters = models.JSONField(default=dict) | |
| status = models.CharField( | |
| max_length=20, | |
| choices=STATUS_CHOICES, | |
| default='PENDING' | |
| ) | |
| progress = models.IntegerField(default=0) | |
| logs = models.TextField(blank=True) | |
| result_data = models.JSONField(null=True, blank=True) | |
| error_message = models.TextField(blank=True) | |
| input_file = models.FileField( | |
| upload_to='simulations/inputs/', | |
| null=True, | |
| blank=True | |
| ) | |
| output_file = models.FileField( | |
| upload_to='simulations/outputs/', | |
| null=True, | |
| blank=True | |
| ) | |
| plot_file = models.FileField( | |
| upload_to='simulations/plots/', | |
| null=True, | |
| blank=True | |
| ) | |
| pdf_file = models.FileField( | |
| upload_to='simulations/reports/', | |
| null=True, | |
| blank=True | |
| ) | |
| csv_file = models.FileField( | |
| upload_to='simulations/csv/', | |
| null=True, | |
| blank=True | |
| ) | |
| created_by = models.ForeignKey( | |
| settings.AUTH_USER_MODEL, | |
| on_delete=models.SET_NULL, | |
| null=True, | |
| blank=True | |
| ) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| started_at = models.DateTimeField(null=True, blank=True) | |
| completed_at = models.DateTimeField(null=True, blank=True) | |
| class Meta: | |
| ordering = ['-created_at'] | |
| def __str__(self): | |
| return self.name or f"Run #{self.id} - {self.method.name}" | |
| def add_log(self, message): | |
| from django.utils import timezone | |
| timestamp = timezone.now().strftime('%Y-%m-%d %H:%M:%S') | |
| self.logs += f"[{timestamp}] {message}\n" | |
| self.save(update_fields=['logs']) | |
| def set_running(self): | |
| from django.utils import timezone | |
| self.status = 'RUNNING' | |
| self.started_at = timezone.now() | |
| self.save(update_fields=['status', 'started_at']) | |
| def set_success(self, result_data=None): | |
| from django.utils import timezone | |
| self.status = 'SUCCESS' | |
| self.progress = 100 | |
| self.completed_at = timezone.now() | |
| if result_data: | |
| self.result_data = result_data | |
| self.save(update_fields=['status', 'progress', 'completed_at', 'result_data']) | |
| self.generate_outputs() | |
| def set_failure(self, error_message): | |
| from django.utils import timezone | |
| self.status = 'FAILURE' | |
| self.completed_at = timezone.now() | |
| self.error_message = error_message | |
| self.save(update_fields=['status', 'completed_at', 'error_message']) | |
| def set_pending(self): | |
| self.status = 'PENDING' | |
| self.progress = 0 | |
| self.started_at = None | |
| self.completed_at = None | |
| self.save(update_fields=['status', 'progress', 'started_at', 'completed_at']) | |
| def generate_plot(self): | |
| """Génère un graphique selon la méthode.""" | |
| if not self.result_data: | |
| return None | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| method_slug = self.method.slug | |
| if method_slug == 'monte-carlo-pi': | |
| self._plot_monte_carlo(fig, ax) | |
| elif method_slug == 'diffusion-1d': | |
| self._plot_diffusion(fig, ax) | |
| elif method_slug == 'linear-solve': | |
| self._plot_linear(fig, ax) | |
| elif method_slug == 'heat-conduction': | |
| self._plot_heat_conduction(fig, ax) | |
| elif method_slug == 'traffic-flow': | |
| self._plot_traffic_flow(fig, ax) | |
| elif method_slug == 'phugoid': | |
| self._plot_phugoid(fig, ax) | |
| elif method_slug == 'elasticity-dangvan': | |
| self._plot_elasticity(fig, ax) | |
| elif method_slug == 'wave-equation': | |
| self._plot_wave_equation(fig, ax) | |
| else: | |
| ax.text(0.5, 0.5, 'Pas de visualisation disponible', | |
| ha='center', va='center', transform=ax.transAxes) | |
| plt.tight_layout() | |
| buffer = io.BytesIO() | |
| plt.savefig(buffer, format='png', dpi=150) | |
| buffer.seek(0) | |
| plt.close(fig) | |
| return buffer | |
| def _plot_monte_carlo(self, fig, ax): | |
| """Graphique pour Monte Carlo.""" | |
| data = self.result_data | |
| ax.bar(['Estimé', 'Exact'], [data['pi_estimate'], data['exact_pi']], | |
| color=['#3498db', '#27ae60']) | |
| ax.set_ylabel('Valeur de Pi') | |
| ax.set_title(f"Estimation de Pi - {data['n_points']} points") | |
| ax.set_ylim(0, 4) | |
| error_pct = abs(data['error']) / data['exact_pi'] * 100 | |
| ax.text(0.5, 0.9, f"Erreur: {error_pct:.4f}%", | |
| transform=ax.transAxes, ha='center') | |
| def _plot_diffusion(self, fig, ax): | |
| """Graphique pour diffusion 1D.""" | |
| data = self.result_data | |
| final_state = data.get('final_state', []) | |
| if final_state: | |
| x = np.linspace(0, len(final_state) - 1, len(final_state)) | |
| ax.plot(x, final_state, 'b-', linewidth=2, label='État final') | |
| ax.set_xlabel('Position x') | |
| ax.set_ylabel('Concentration u(x,t)') | |
| ax.set_title('Profil de diffusion 1D - État final') | |
| ax.grid(True, alpha=0.3) | |
| ax.legend() | |
| ax.fill_between(x, final_state, alpha=0.3) | |
| ax.set_xlim(0, len(final_state) if final_state else 100) | |
| def _plot_linear(self, fig, ax): | |
| """Graphique pour résolution linéaire.""" | |
| data = self.result_data | |
| info = f"Taille: {data['size']}\n" | |
| info += f"Norme solution: {data['solution_norm']:.4f}\n" | |
| info += f"Résidu: {data['residual']:.2e}\n" | |
| info += f"Nombre de condition: {data['condition_number']:.2e}" | |
| ax.text(0.5, 0.5, info, transform=ax.transAxes, ha='center', va='center', | |
| fontsize=12, bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) | |
| ax.set_title('Résultats - Résolution système linéaire') | |
| def _plot_heat_conduction(self, fig, ax): | |
| """Graphique pour conduction thermique.""" | |
| data = self.result_data | |
| T_final = np.array(data.get('final_T', [])) | |
| T_analytical = np.array(data.get('T_analytical', [])) | |
| N = len(T_final) | |
| t = np.arange(N) * data.get('dt', 0.001) | |
| ax.plot(t, T_final, 'b-', linewidth=2, label='Numérique') | |
| ax.plot(t, T_analytical, 'r--', linewidth=2, label='Analytique') | |
| ax.set_xlabel('Temps') | |
| ax.set_ylabel('Température') | |
| ax.set_title(f"Conduction thermique - θ={data.get('theta', 0.5)}") | |
| ax.legend() | |
| ax.grid(True, alpha=0.3) | |
| def _plot_traffic_flow(self, fig, ax): | |
| """Graphique pour flux de trafic.""" | |
| data = self.result_data | |
| rho_matrix = np.array(data.get('density_matrix', [])) | |
| if rho_matrix.size > 0: | |
| im = ax.imshow(rho_matrix, aspect='auto', cmap='hot', origin='lower') | |
| ax.set_xlabel('Position x') | |
| ax.set_ylabel('Temps t') | |
| ax.set_title('Densité de trafic') | |
| plt.colorbar(im, ax=ax, label='Densité') | |
| else: | |
| ax.text(0.5, 0.5, 'Pas de données', ha='center', va='center') | |
| def _plot_phugoid(self, fig, ax): | |
| """Graphique pour trajectoire phugoid.""" | |
| data = self.result_data | |
| x = np.array(data.get('x', [])) | |
| z = np.array(data.get('z', [])) | |
| if x.size > 0: | |
| ax.plot(x, -z, 'b-', linewidth=2) | |
| ax.set_xlabel('x') | |
| ax.set_ylabel('z') | |
| ax.set_title(f"Trajectoire de vol - C={data.get('C', 0):.3f}") | |
| ax.grid(True, alpha=0.3) | |
| ax.set_aspect('equal') | |
| def _plot_elasticity(self, fig, ax): | |
| """Graphique pour élasticité DangVan.""" | |
| data = self.result_data | |
| tensor = np.array(data.get('tensor_matrix', [])).reshape(3, 3) | |
| im = ax.imshow(tensor, cmap='coolwarm', aspect='equal') | |
| ax.set_title('Tenseur des contraintes (MPa)') | |
| for i in range(3): | |
| for j in range(3): | |
| ax.text(j, i, f'{tensor[i,j]:.1f}', ha='center', va='center', color='black') | |
| plt.colorbar(im, ax=ax) | |
| def _plot_wave_equation(self, fig, ax): | |
| """Graphique pour équation d'onde.""" | |
| data = self.result_data | |
| u = np.array(data.get('displacement_matrix', [])) | |
| if u.size > 0: | |
| im = ax.imshow(u, aspect='auto', cmap='RdBu', origin='lower') | |
| ax.set_xlabel('Position x') | |
| ax.set_ylabel('Temps t') | |
| ax.set_title('Déplacement u(x,t)') | |
| plt.colorbar(im, ax=ax, label='u') | |
| def generate_csv(self): | |
| """Génère un fichier CSV des résultats.""" | |
| if not self.result_data: | |
| return None | |
| buffer = io.StringIO() | |
| writer = csv.writer(buffer) | |
| writer.writerow(['Simulation Run Report']) | |
| writer.writerow(['ID', self.id]) | |
| writer.writerow(['Méthode', self.method.name]) | |
| writer.writerow(['Statut', self.status]) | |
| writer.writerow(['Créé le', self.created_at]) | |
| writer.writerow(['Terminé le', self.completed_at]) | |
| writer.writerow([]) | |
| writer.writerow(['Paramètres']) | |
| for key, value in self.parameters.items(): | |
| writer.writerow([key, str(value)]) | |
| writer.writerow([]) | |
| writer.writerow(['Résultats']) | |
| for key, value in self.result_data.items(): | |
| writer.writerow([key, str(value)]) | |
| buffer.seek(0) | |
| return io.BytesIO(buffer.getvalue().encode('utf-8')) | |
| def generate_pdf(self): | |
| """Génère un rapport PDF avec ReportLab.""" | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.lib import colors | |
| from reportlab.lib.units import inch | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.enums import TA_CENTER, TA_LEFT | |
| import tempfile | |
| buffer = io.BytesIO() | |
| doc = SimpleDocTemplate(buffer, pagesize=A4, | |
| leftMargin=0.5*inch, rightMargin=0.5*inch, | |
| topMargin=0.5*inch, bottomMargin=0.5*inch) | |
| styles = getSampleStyleSheet() | |
| title_style = ParagraphStyle('Title', parent=styles['Heading1'], | |
| fontSize=18, spaceAfter=20, alignment=TA_CENTER) | |
| heading_style = ParagraphStyle('Heading', parent=styles['Heading2'], | |
| fontSize=14, spaceAfter=10, color=colors.darkblue) | |
| normal_style = styles['Normal'] | |
| story = [] | |
| story.append(Paragraph(f"Rapport de Simulation: {self.method.name}", title_style)) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Informations Générales", heading_style)) | |
| info_data = [ | |
| ['ID de la simulation', str(self.id)], | |
| ['Méthode', self.method.name], | |
| ['Statut', self.status], | |
| ['Créé le', str(self.created_at)], | |
| ['Terminé le', str(self.completed_at)], | |
| ] | |
| info_table = Table(info_data, colWidths=[2.5*inch, 3*inch]) | |
| info_table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), | |
| ('TEXTCOLOR', (0, 0), (0, -1), colors.black), | |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), | |
| ('PADDING', (0, 0), (-1, -1), 6), | |
| ])) | |
| story.append(info_table) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Paramètres de Simulation", heading_style)) | |
| params_data = [[str(k), str(v)] for k, v in self.parameters.items()] | |
| if params_data: | |
| params_table = Table(params_data, colWidths=[2.5*inch, 3*inch]) | |
| params_table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (0, -1), colors.lightblue), | |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), | |
| ('PADDING', (0, 0), (-1, -1), 6), | |
| ])) | |
| story.append(params_table) | |
| else: | |
| story.append(Paragraph("Aucun paramètre personnalisé", normal_style)) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Résultats", heading_style)) | |
| results_data = [[str(k), str(v)] for k, v in self.result_data.items()] | |
| if results_data: | |
| results_table = Table(results_data, colWidths=[2.5*inch, 3*inch]) | |
| results_table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (0, -1), colors.lightgreen), | |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), | |
| ('PADDING', (0, 0), (-1, -1), 6), | |
| ])) | |
| story.append(results_table) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Graphique", heading_style)) | |
| import os | |
| if self.plot_file and os.path.exists(self.plot_file.path): | |
| img = Image(self.plot_file.path, width=5*inch, height=3*inch) | |
| img.hAlign = 'CENTER' | |
| story.append(img) | |
| else: | |
| story.append(Paragraph("Graphique non disponible", normal_style)) | |
| story.append(Spacer(1, 20)) | |
| if self.logs: | |
| story.append(Paragraph("Logs d'Éxecution", heading_style)) | |
| log_style = ParagraphStyle('Log', parent=styles['Normal'], | |
| fontSize=8, textColor=colors.darkgrey) | |
| for line in self.logs.strip().split('\n')[-50:]: | |
| story.append(Paragraph(line, log_style)) | |
| story.append(Spacer(1, 30)) | |
| story.append(Paragraph(f"Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", | |
| ParagraphStyle('Date', parent=styles['Normal'], | |
| fontSize=8, alignment=TA_CENTER))) | |
| doc.build(story) | |
| buffer.seek(0) | |
| return buffer | |
| def generate_outputs(self): | |
| """Génère tous les fichiers de sortie.""" | |
| import os | |
| plot_buffer = self.generate_plot() | |
| if plot_buffer: | |
| filename = f"plot_{self.id}.png" | |
| self.plot_file.save(filename, plot_buffer, save=True) | |
| csv_buffer = self.generate_csv() | |
| if csv_buffer: | |
| filename = f"results_{self.id}.csv" | |
| self.csv_file.save(filename, csv_buffer, save=True) | |
| if self.plot_file: | |
| pdf_buffer = self.generate_pdf_with_plot() | |
| if pdf_buffer: | |
| filename = f"report_{self.id}.pdf" | |
| self.pdf_file.save(filename, pdf_buffer, save=True) | |
| def generate_pdf_with_plot(self): | |
| """Génère un rapport PDF avec le graphique déjà sauvegardé.""" | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.lib import colors | |
| from reportlab.lib.units import inch | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.enums import TA_CENTER, TA_LEFT | |
| import os | |
| if not self.plot_file or not os.path.exists(self.plot_file.path): | |
| return None | |
| buffer = io.BytesIO() | |
| doc = SimpleDocTemplate(buffer, pagesize=A4, | |
| leftMargin=0.5*inch, rightMargin=0.5*inch, | |
| topMargin=0.5*inch, bottomMargin=0.5*inch) | |
| styles = getSampleStyleSheet() | |
| title_style = ParagraphStyle('Title', parent=styles['Heading1'], | |
| fontSize=18, spaceAfter=20, alignment=TA_CENTER) | |
| heading_style = ParagraphStyle('Heading', parent=styles['Heading2'], | |
| fontSize=14, spaceAfter=10, color=colors.darkblue) | |
| normal_style = styles['Normal'] | |
| story = [] | |
| story.append(Paragraph(f"Rapport de Simulation: {self.method.name}", title_style)) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Informations Générales", heading_style)) | |
| info_data = [ | |
| ['ID de la simulation', str(self.id)], | |
| ['Méthode', self.method.name], | |
| ['Statut', self.status], | |
| ['Créé le', str(self.created_at)], | |
| ['Terminé le', str(self.completed_at)], | |
| ] | |
| info_table = Table(info_data, colWidths=[2.5*inch, 3*inch]) | |
| info_table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), | |
| ('TEXTCOLOR', (0, 0), (0, -1), colors.black), | |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), | |
| ('PADDING', (0, 0), (-1, -1), 6), | |
| ])) | |
| story.append(info_table) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Paramètres de Simulation", heading_style)) | |
| params_data = [[str(k), str(v)] for k, v in self.parameters.items()] | |
| if params_data: | |
| params_table = Table(params_data, colWidths=[2.5*inch, 3*inch]) | |
| params_table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (0, -1), colors.lightblue), | |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), | |
| ('PADDING', (0, 0), (-1, -1), 6), | |
| ])) | |
| story.append(params_table) | |
| else: | |
| story.append(Paragraph("Aucun paramètre personnalisé", normal_style)) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Résultats", heading_style)) | |
| if self.result_data: | |
| results_data = [[str(k), str(v)] for k, v in self.result_data.items()] | |
| if results_data: | |
| results_table = Table(results_data, colWidths=[2.5*inch, 3*inch]) | |
| results_table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (0, -1), colors.lightgreen), | |
| ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), | |
| ('PADDING', (0, 0), (-1, -1), 6), | |
| ])) | |
| story.append(results_table) | |
| story.append(Spacer(1, 20)) | |
| story.append(Paragraph("Graphique", heading_style)) | |
| img = Image(self.plot_file.path, width=5*inch, height=3*inch) | |
| img.hAlign = 'CENTER' | |
| story.append(img) | |
| story.append(Spacer(1, 20)) | |
| if self.logs: | |
| story.append(Paragraph("Logs d'Éxecution", heading_style)) | |
| log_style = ParagraphStyle('Log', parent=styles['Normal'], | |
| fontSize=8, textColor=colors.darkgrey) | |
| for line in self.logs.strip().split('\n')[-50:]: | |
| story.append(Paragraph(line, log_style)) | |
| story.append(Spacer(1, 30)) | |
| story.append(Paragraph(f"Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", | |
| ParagraphStyle('Date', parent=styles['Normal'], | |
| fontSize=8, alignment=TA_CENTER))) | |
| doc.build(story) | |
| buffer.seek(0) | |
| return buffer | |
Xet Storage Details
- Size:
- 20.4 kB
- Xet hash:
- f746217e2bad2cc1a6f853594872f5e4a10539b4336df35bf13d314ec4d9d40a
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.