# ====================================================================
# src/corelab/simulations.py - Módulo de Lógica de Simulación
# ====================================================================
# Contiene todas las funciones para extraer propiedades de PDFs,
# detectar tipos de material y generar curvas sintéticas de
# esfuerzo-deformación para diferentes ensayos mecánicos.
# ====================================================================
import numpy as np
import pandas as pd
import os
import re
try:
import fitz # PyMuPDF
except ImportError:
fitz = None
# --- Módulos de Simulación Principales ---
[documentos]
def simular_polimero(E, Sy, Su, εf, G, **kwargs):
"""Orquesta la simulación completa para un material polimérico.
Utiliza modelos que simulan el comportamiento característico de los polímeros,
incluyendo el fenómeno de "cold drawing".
Args:
E (float): Módulo de Young (Pa).
Sy (float): Límite de fluencia (Pa).
Su (float): Resistencia máxima a la tracción (Pa).
εf (float): Deformación en el punto de fractura.
G (float): Módulo de cizalladura (Pa).
**kwargs: Argumentos adicionales.
Returns:
dict: Diccionario con las curvas de simulación generadas.
"""
t_t, ε_t, σ_t = curva_tension_polimero(E, Sy, Su, εf)
t_c, ε_c, σ_c = curva_compresion_polimero(E, Sy)
t_tor, γ_tor, τ_tor = curva_torsion_ductil(G, Sy, Su, εf)
return {"tension": (t_t, ε_t, σ_t), "compresion": (t_c, ε_c, σ_c), "torsion": (t_tor, γ_tor, τ_tor)}
[documentos]
def simular_ceramico(E, Su_tension, Su_compresion, G, **kwargs):
"""Orquesta la simulación para un material cerámico frágil.
Utiliza modelos de comportamiento elástico lineal hasta la fractura,
que es característico de los materiales cerámicos.
Args:
E (float): Módulo de Young (Pa).
Su_tension (float): Resistencia a la fractura por tensión (Pa).
Su_compresion (float): Resistencia a la fractura por compresión (Pa).
G (float): Módulo de cizalladura (Pa).
**kwargs: Argumentos adicionales.
Returns:
dict: Diccionario con las curvas de simulación generadas.
"""
t_t, ε_t, σ_t = curva_lineal(E, Su_tension)
t_c, ε_c, σ_c = curva_lineal(E, -Su_compresion) # Esfuerzo negativo para compresión
t_tor, γ_tor, τ_tor = curva_torsion_fragil(G, Su_tension)
return {"tension": (t_t, ε_t, σ_t), "compresion": (t_c, ε_c, σ_c), "torsion": (t_tor, γ_tor, τ_tor)}
[documentos]
def curva_lineal(E, Su, p=100):
"""Genera una curva elástica simple hasta la fractura para materiales frágiles.
Args:
E (float): Módulo de Young (Pa).
Su (float): Resistencia a la fractura (Pa).
p (int): Número de puntos a generar.
Returns:
tuple: (tiempo, deformacion, esfuerzo)
"""
deformacion = np.linspace(0, Su / E, p)
esfuerzo = E * deformacion
tiempo = np.linspace(0, len(deformacion) * 0.1, len(deformacion))
return tiempo, deformacion, esfuerzo
# --- Funciones de Generación de Curvas ---
[documentos]
def curva_torsion_ductil(G, Sy, Su, εf, p=300):
"""Genera una curva de torsión sintética para materiales dúctiles."""
τy=Sy/np.sqrt(3); τu=Su/np.sqrt(3); γy=τy/G; γf=εf*1.7; p1=int(p*0.1); γ1=np.linspace(0,γy,p1,endpoint=False); τ1=G*γ1; p2=p-p1; γ2=np.linspace(γy,γf,p2); τ2=τy+(τu-τy)*(((γ2-γy)/(γf-γy))**0.2) if (γf-γy)>0 else τy;
deformacion = np.concatenate((γ1,γ2)); esfuerzo = np.concatenate((τ1,τ2))
tiempo = np.linspace(0, len(deformacion) * 0.1, len(deformacion))
return tiempo, deformacion, esfuerzo
[documentos]
def curva_torsion_fragil(G, Su_tension, p=100):
"""Genera una curva de torsión sintética para materiales frágiles."""
τ_max=Su_tension*0.9; γ_fractura=τ_max/G; deformacion=np.linspace(0,γ_fractura,p); esfuerzo=G*deformacion
tiempo = np.linspace(0, len(deformacion) * 0.1, len(deformacion))
return tiempo, deformacion, esfuerzo
[documentos]
def curva_tension_polimero(E, Sy, Su, εf, p=400):
"""Genera una curva de tensión sintética para polímeros."""
εy=Sy/E; Su_draw=Sy*0.8; ε_cold_draw_end=εf*0.8; p1=int(p*0.05); ε1=np.linspace(0,εy,p1); σ1=E*ε1; p2=int(p*0.1); ε2=np.linspace(εy,εy*2,p2); σ2=Sy-(Sy-Su_draw)*((ε2-εy)/εy)**0.5; p3=int(p*0.75); ε3=np.linspace(εy*2,ε_cold_draw_end,p3); σ3=np.full_like(ε3,Su_draw); p4=p-p1-p2-p3; ε4=np.linspace(ε_cold_draw_end,εf,p4); σ4=Su_draw+(Su-Su_draw)*((ε4-ε_cold_draw_end)/(εf-ε_cold_draw_end));
deformacion = np.concatenate((ε1,ε2,ε3,ε4)); esfuerzo = np.concatenate((σ1,σ2,σ3,σ4))
tiempo = np.linspace(0, len(deformacion) * 0.1, len(deformacion))
return tiempo, deformacion, esfuerzo
[documentos]
def curva_compresion_polimero(E, Sy, p=200):
"""Genera una curva de compresión sintética para polímeros."""
εy=Sy/E; p1=int(p*0.2); ε1=np.linspace(0,-εy,p1); σ1=E*ε1; p2=p-p1; ε2=np.linspace(-εy,-εy*10,p2); σ2=-Sy-(E*0.05)*(abs(ε2)-εy)**0.8;
deformacion = np.concatenate((ε1,ε2)); esfuerzo = np.concatenate((σ1,σ2))
tiempo = np.linspace(0, len(deformacion) * 0.1, len(deformacion))
return tiempo, deformacion, esfuerzo
# --- Funciones de Extracción y Detección ---
[documentos]
def identify_material_type(text):
"""Identifica el tipo de material a partir de un texto.
Busca palabras clave en el texto extraído del PDF para clasificar
el material en 'metal', 'polimero' o 'ceramico'.
Args:
text (str): El texto completo extraído de un PDF.
Returns:
str: El tipo de material detectado. Devuelve 'metal' como valor por defecto.
"""
text=text.lower();
if any(k in text for k in['steel','iron','aluminum','alloy']): return "metal"
if any(k in text for k in['polymer','polyethylene','nylon','abs','pvc']): return "polimero"
if any(k in text for k in['ceramic','alumina','zirconia','glass','concrete','cement']): return "ceramico"
return "metal"
[documentos]
def get_properties_from_pdf(file_path):
"""Extrae propiedades mecánicas clave de un archivo PDF y detecta el tipo de material.
Esta función está optimizada para leer PDFs con formatos de ficha técnica
similares a los de MatWeb.com. Utiliza una biblioteca de sinónimos y
expresiones regulares para encontrar los valores.
Args:
file_path (str): La ruta completa al archivo PDF a procesar.
Returns:
tuple: Una tupla `(nombre, propiedades, tipo_material)` en caso de éxito.
- **nombre** (*str*): El nombre del material extraído y sanitizado.
- **propiedades** (*dict*): Un diccionario con las propiedades mecánicas.
- **tipo_material** (*str*): El tipo de material detectado.
Si el procesamiento falla, devuelve `(None, None, None)`.
"""
if not fitz:
print("Error: PyMuPDF no está instalado.")
return None, None, None
try:
doc = fitz.open(file_path)
text = "".join(page.get_text() for page in doc)
doc.close()
name_raw = os.path.basename(file_path).replace('.pdf', '').replace(',', '')
name = name_raw.replace(' ', '_')
mtype = identify_material_type(text)
props = {}
synonym_library = {"E":["Modulus of Elasticity","Tensile Modulus","Young's Modulus","Flexural Modulus"],"Su":["Tensile Strength, Ultimate","Ultimate Tensile Strength"],"Sy":["Tensile Strength, Yield","Yield Strength","Flexural Yield Strength"],"G":["Shear Modulus"],"εf":["Elongation at Break"],"Su_tension":["Tensile Strength, Ultimate","Flexural Strength","Tensile Strength"],"Su_compresion":["Compressive Strength","Ultimate Compressive Strength","Crushing Strength"]}
required_keys = []
if mtype == "metal": required_keys=['E','Sy','Su','G','εf']
elif mtype == "polimero": required_keys=['E','Sy','Su','εf']
elif mtype == "ceramico": required_keys=['E','Su_tension','Su_compresion']
search_keys = list(set(required_keys + ['G']))
for key in search_keys:
name_patterns_regex = "|".join([s.replace(" ", r"\s*") for s in synonym_library.get(key, [])])
if not name_patterns_regex: continue
avg_pattern = re.compile(rf"(?:{name_patterns_regex})[\s\S]*?Average value:\s*([\d\.]+)\s*(GPa|MPa|%)", re.IGNORECASE)
match = avg_pattern.search(text)
if not match:
fallback_pattern = re.compile(rf"(?:{name_patterns_regex})[\s\S]*?([\d\.]+)\s*(GPa|MPa|%)", re.IGNORECASE)
match = fallback_pattern.search(text)
if match:
val, unit = float(match.group(1)), match.group(2)
if "GPa" in unit: val *= 1e9
elif "MPa" in unit: val *= 1e6
elif "%" in unit: val /= 100
props[key] = val
return name, props, mtype
except Exception as e:
print(f"❌ Error al procesar '{os.path.basename(file_path)}': {e}")
return None, None, None