Geral
Os estudos paramétricos são um meio sensato e eficaz para adaptar simulações. Os objetivos podem ser de natureza variada: a descoberta de uma estrutura ideal com peso mínimo, garantindo simultaneamente a segurança e poupando recursos, a adaptação da finura da malha para obter resultados de simulação fiáveis, ou a variação das propriedades de material para verificar a sensibilidade da simulação e poder fazer afirmações fiáveis sobre a segurança estrutural. Para estes casos e muitos outros, uma modelação parametrizada é a solução ideal. Para não ter de calcular todas as variantes individualmente, pode ser utilizada a API da Dlubal. Desta forma, o cálculo do estudo paramétrico pode decorrer sem necessidade de supervisão.
Modelação parametrizada
Muitas funções no RFEM 6, RSTAB 9 e RSECTION já são parametrizáveis. Mais informações podem ser encontradas nos respetivos manuais:
- Manuais online RFEM 6 | Função do produto | Entrada parametrizada
- Manuais online RSTAB 9 | Função do produto | Entrada parametrizada
- Manual online do RSECTION 1 | Funções do produto | Entrada parametrizada
Acesso ao modelo através da API
Para poder aceder ao modelo através da API, o pacote correspondente tem de estar instalado. Em alternativa a um editor externo, também pode utilizar diretamente a consola ou o gestor de scripts no programa principal. Mais informações estão disponíveis nos links seguintes.
Exemplo 1: Otimização de espessuras de parede
Este modelo é uma caixa simples com um cilindro cónico sobreposto. Ambas as peças são feitas de chapa de aço com diferentes espessuras de parede. Estas foram parametrizadas para realizar um estudo paramétrico. O objetivo aqui é encontrar a configuração cuja tensão equivalente de Von Mises, com peso mínimo, não exceda o limite de elasticidade. As cargas atuantes são o peso próprio e, num caso de carga de carga variável, uma carga pontual por componentes numa superfície rígida no topo do cone. O apoio é realizado de forma articulada em duas linhas na parte inferior da caixa. O modelo e uma vista da interface de utilizador com a parametrização podem ser consultados a seguir.
O script seguinte está dividido em secções. Na primeira secção, são importadas as bibliotecas utilizadas, seguindo-se a definição das variáveis. Aqui, o limite de elasticidade é indicado para fins de visualização, bem como a gama de variação dos parâmetros a variar, que estão ligados à espessura da chapa. São então definidas funções, que serão utilizadas mais tarde para a construção da matriz de variantes e a alteração dos parâmetros globais no RFEM 6.
A parte principal do programa começa com a construção da matriz de variantes dos parâmetros globais. Para tal, todas as mutações possíveis são geradas a partir das variações de parâmetros definidas por meio do produto cartesiano. Posteriormente, estas são ainda restringidas pela condição de que a espessura da parede na área inferior deve ser maior ou igual à da estrutura superior.
De seguida, é estabelecida a ligação com o RFEM 6 através da API da Dlubal. Neste caso, para o modelo ativo com a chave API depositada no ficheiro de configuração. Ambas as definições também podem ser especificadas na chamada da função.
Agora, é executado um ciclo no qual, sucessivamente, os parâmetros globais no RFEM 6 são adaptados de acordo com a mutação atual, o cálculo é realizado e os resultados são lidos. Para forçar uma adaptação do modelo e a eliminação dos resultados, a malha é eliminada e gerada de novo antes do cálculo. O máximo da tensão equivalente de Von Mises de todas as superfícies é determinado a partir dos resultados da primeira situação de dimensionamento. O peso total da estrutura é determinado a partir da força resultante na direção z no caso de carga de peso próprio. A partir desta, o peso é calculado pela divisão da aceleração gravítica.
Nas duas últimas secções, os resultados são guardados como uma tabela Excel e representados graficamente como um diagrama da tensão em relação ao peso da estrutura.
"""ParaS-VMStress_Thick-min.py
Modelo RFEM 6 relacionado: "Para_Plate_Joint.rf6"
Modelo: https://www.dlubal.com/en/downloads-and-information/examples-and-tutorials/models-to-download/006159
Executa um estudo paramétrico através da API RFEM da Dlubal, recolhe métricas de resultado, exporta e traça resultados.
e gráfico.
Secções:
- Imports
- Configuração
- Funções
- Execução principal
- Criação de gráficos
"""
# --------------------- Imports ---------------------
import itertools
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from dlubal.api import rfem
# --------------------- Configuração ---------------------
gamas_de_parametros = {
't_1': {'min': 8, 'max': 12, 'step': 2},
't_2': {'min': 4, 'max': 8, 'step': 2},
} # espessuras em mm
f_y = 235 # tensão de cedência em MPa
# --------------------- Funções ---------------------
def construir_grelha_parametros(gamas_param):
"""Constrói um conjunto de dados de mutação completo a partir de valores min/max/passo em mm."""
colunas = []
listas_valores = []
for nome, limites in gamas_param.items():
val_min = limites['min']
val_max = limites['max']
passo = limites['step']
if passo <= 0:
raise ValueError(f"A largura do passo para '{nome}' deve ser positiva.")
if val_max < val_min:
raise ValueError(f"O valor máximo para '{nome}' deve ser >= valor mínimo.")
valores = np.arange(val_min, val_max + passo * 0.5, passo)
colunas.append(nome)
listas_valores.append(valores)
combinacoes = list(itertools.product(*listas_valores))
return pd.DataFrame(combinacoes, columns=colunas)
def set_glpa(dl_app, p_name, p_value,):
"""Define o parâmetro global p_name para o valor especificado p_value"""
params = dl_app.get_object_list([rfem.global_parameters.GlobalParameter()])
for p in params:
if p.name == p_name:
p.value = p_value
dl_app.update_object(p)
check=True
return True
if not check:
raise ValueError(f" Erro: Parâmetro '{p_name}' não encontrado!")
# --------------------- Execução principal ---------------------
# Construir conjunto de parâmetros
para_dfo = construir_grelha_parametros(gamas_de_parametros)
para_dfo = para_dfo[para_dfo['t_1'] >= para_dfo['t_2']]
para_df = para_dfo / 1000 # mm -> m
print(f"Conjunto de parâmetros:\n {para_dfo}")
# Ligar ao RFEM com o modelo atual e executar estudo paramétrico
with rfem.Application() as rf_app:
# Verificar ligação e imprimir informações do modelo
app_info = rf_app.get_application_info()
print("Informação da Aplicação:", app_info)
res_paras=pd.DataFrame(columns=['sigvm','sfz'])
for i in para_df.index:
# definir parâmetros globais
print(f"Definir mutação {i} com parâmetros {[x for x in para_df.loc[i]]}")
for p in para_df.columns:
# print(f"Definir mutação {i} com parâmetro {p} para {para_df.loc[i, p]}")
if not set_glpa(rf_app, p, para_df.loc[i, p]):
break
# Remalhar
rf_app.delete_mesh()
rf_app.generate_mesh(skip_warnings=True)
# Executar cálculo
calculation = rf_app.calculate_all(skip_warnings=True)
# Resultados
if calculation.succeeded:
# Tensões de Von Mises
res_sigvm = rf_app.get_results(
results_type=rfem.results.STATIC_ANALYSIS_SURFACES_EQUIVALENT_STRESSES_MISES_MESH_NODES,
filters=[
rfem.results.ResultsFilter(column_id='loading', filter_expression='DS1'),
]
).data
sigvm = res_sigvm['sigma_eqv_mises'].max()/10**6 # N/m^2 -> MPa
# peso próprio
sfz = rf_app.get_result_table(
table=rfem.results.ResultTable.STATIC_ANALYSIS_SUMMARY_TABLE,
loading=rfem.ObjectId(no=1,object_type=rfem.OBJECT_TYPE_LOAD_CASE)
).data
sfz = float(sfz.loc[6].value) / 10 # N -> kg (g=10 m/s^2 para aceleração gravítica)
res_paras.loc[i] = pd.Series({'sigvm':sigvm,'sfz':sfz})
else:
print(f"Cálculo falhou para a mutação {i} com parâmetros {[x for x in para_df.loc[i]]}")
res_paras.loc[i] = pd.Series({'sigvm':np.nan,'sfz':np.nan})
# Juntar parâmetros e resultados para exportação e criação de gráficos
para_out = pd.concat([para_dfo,res_paras], axis=1)
# ---------------------- Exportar Resultados ---------------------
para_out.to_excel('./ParaS-VMStress_Thick-min_results.xlsx')
# --------------------- Criação de gráficos ---------------------
fig, ax = plt.subplots(figsize=(12, 9))
ax.scatter(para_out['sfz'], para_out['sigvm'], color='tab:blue', s=50, label='Mutações')
y_offset = (para_out['sigvm'].max() - para_out['sigvm'].min()) * 0.03
for x, y, mut, t1, t2 in zip(para_out['sfz'], para_out['sigvm'], para_out.index, para_out['t_1'], para_out['t_2']):
ax.text(x , y+y_offset, f"M{int(mut)}, {t1:.0f}, {t2:.0f} mm", fontsize=8, va='top', ha='center')
ax.axhline(y=f_y, color='red', linestyle='--', linewidth=1, label=f'Limite de elasticidade ({f_y} MPa)')
ax.set_title('Tensão vs. Massa para Mutações de Parâmetros')
ax.set_xlabel('Massa [kg]')
ax.set_ylabel('Tensão de Von Mises [MPa]')
ax.legend()
fig.tight_layout()
fig.savefig('./ParaS-VMStress_Thick-min_results.png', dpi=200)
plt.show()
Como se pode ver neste estudo paramétrico simples, em relação à configuração inicial com as espessuras de parede de 12 e 8 mm, com espessuras de parede de 8 mm cada, é possível poupar cerca de 30 % de material sem exceder o limite de elasticidade admissível. Os resultados podem ser vistos na figura seguinte.
Exemplo 2: Estudo de convergência da malha
Este exemplo já foi utilizado para o artigo técnico sobre estudos de convergência da malha. Trata-se de uma casca cilíndrica para a qual é realizada uma análise de encurvadura linear. O link para o artigo técnico, que contém uma descrição mais detalhada, e o modelo associado podem ser consultados a seguir:
O objetivo de um estudo de convergência da malha é ajustar a finura da malha para que não ocorra mais nenhuma alteração relevante nos resultados, sem atingir um número demasiado elevado de elementos, de modo a permitir um trabalho económico. Para tal, foi introduzido um parâmetro global (lfe) no presente modelo. Este foi depois passado para o refinamento da malha de superfície como tamanho da malha.
No script seguinte, o estudo de convergência da malha é realizado através da alteração gradual do tamanho de malha EF pretendido. A sua estrutura básica corresponde à do primeiro exemplo, pelo que aqui apenas serão abordadas as diferenças.
Para analisar a influência da finura da malha no tempo de cálculo, este é determinado a partir do tempo de processo da chamada de cálculo. Os fatores de carga críticos são determinados a partir da análise de estabilidade do primeiro caso de carga. De seguida, é efetuada uma determinação da alteração do fator de carga em relação ao passo anterior, para poder forçar uma paragem se a amplitude de variação for maior. Antes da emissão dos resultados, é realizada uma análise de convergência, que avalia a alteração relativa dos resultados.
"""ParaS-LBA_MeshConvergence-min.py
Modelo RFEM 6 relacionado: "MeshSensitivity-LBA_AlC.rf6"
Modelo: https://www.dlubal.com/en/downloads-and-information/examples-and-tutorials/models-to-download/006080
Executa um estudo de convergência de malha no RFEM variando o parâmetro global
`l_fe`, recolhe o fator de carga crítico, compara variantes, guarda e traça resultados.
Secções:
- Imports
- Configuração
- Execução principal
- Criação de gráficos
"""
# --------------------- Imports ---------------------
# Biblioteca padrão
import time
# Terceiros
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from dlubal.api import rfem
# ------------------- Configuração -------------------
FE_SIZES_MM = [15, 12, 10, 9, 8, 7, 6, 5, 4, 3] # Lista de tamanhos de elementos finitos a variar em milímetros
PARAM_NAME = "l_fe" # Nome do parâmetro global no modelo
CONVERGENCE_THRESHOLD_PCT = 1.0 # Limiar de convergência em percentagem
FCR_ANA = 1065 # Fator de carga crítico analítico (para referência no gráfico)
# ------------------- Funções -------------------
def set_glpa(dl_app, p_name, p_value,):
"""Define o parâmetro global p_name para o valor especificado p_value"""
params = dl_app.get_object_list([rfem.global_parameters.GlobalParameter()])
for p in params:
if p.name == p_name:
p.value = p_value
dl_app.update_object(p)
check=True
return True
if not check:
raise ValueError(f" Erro: Parâmetro '{p_name}' não encontrado!")
# ------------------- Execução principal -------------------
with rfem.Application() as rf_app:
# Verificar ligação e imprimir informações do modelo
app_info = rf_app.get_application_info()
print("Informação da Aplicação:", app_info)
results = pd.DataFrame(columns=['l_fe_mm', 'n_elements', 'c_time_s', 'f_cr', 'delta_pct'])
f_prev = None
i = 0
for l_fe in FE_SIZES_MM:
i += 1
l_fe_m = l_fe / 1000.0 # Converter mm para m para uso no RFEM
if not set_glpa(dl_app=rf_app, p_name=PARAM_NAME, p_value=l_fe_m):
break
# Remalhar
rf_app.delete_mesh()
rf_app.generate_mesh(skip_warnings=True)
n_elem = rf_app.get_mesh_statistics().surface_2D_finite_elements
# Executar cálculo
t0 = time.perf_counter()
calculation = rf_app.calculate_all(skip_warnings=True)
t1 = time.perf_counter()
c_time = t1 - t0
# Resultados
if calculation.succeeded:
f_cr = rf_app.get_results(
results_type=rfem.results.STABILITY_ANALYSIS_CRITICAL_LOAD_FACTORS,
filters=[
rfem.results.ResultsFilter(column_id='loading', filter_expression='LC1'),
]
).data.loc[0].f
if f_prev is not None and f_prev != 0 and f_cr is not None:
delta_pct = abs(f_cr - f_prev) / abs(f_prev) * 100.0
delta_str = f"{delta_pct:>4.2f}"
else:
delta_pct = None
delta_str = f"{'---':>4}"
if f_cr is not None:
print(f"Mutação {i}: l_fe = {l_fe:.1f} mm | n_elements = {n_elem} | f_cr = {f_cr:.2f} | delta = {delta_str} %")
results.loc[i] = pd.Series({
'l_fe_mm': l_fe, 'n_elements': n_elem, 'c_time_s': c_time,
'f_cr': f_cr, 'delta_pct': delta_pct
})
f_prev = f_cr
else:
print(f"Cálculo falhou para a mutação {i} com tamanho de malha {l_fe:.1f} mm")
results.loc[i] = pd.Series({
'l_fe_mm': l_fe, 'n_elements': n_elem, 'c_time_s': np.nan,
'f_cr': np.nan, 'delta_pct': np.nan
})
# Calcular 1 / n_elements para o segundo gráfico
results['inv_n_elements'] = 1.0 / results['n_elements']
# Calcular desvio relativo do fator de carga crítico mínimo
results['rel_dev_f_cr_min'] = (results['f_cr'] - results['f_cr'].min()) / results['f_cr'].min() * 100
# Análise de convergência
print(f"\n Análise de convergência (limiar: < {CONVERGENCE_THRESHOLD_PCT} % alteração):")
convergence_point = None
for (l_mm, n, a, d) in results.loc[:, ['l_fe_mm', 'n_elements', 'f_cr', 'delta_pct']].itertuples(index=False):
if d is not None and d < CONVERGENCE_THRESHOLD_PCT:
convergence_point = (l_mm, n, a)
break
if convergence_point:
print(f" Convergência atingida em l_fe = {convergence_point[0]:.1f} mm")
print(f" f_cr = {convergence_point[2]:.2f} | n_elements = {convergence_point[1]}")
else:
print(" Convergência ainda não atingida – é necessária malha mais fina.")
# ---------------------- Exportar Resultados ---------------------
results.to_excel('./ParaS-LBA_MeshConvergence-min_results.xlsx')
# ------------------- Criação de gráficos -------------------
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Gráfico 1: f_cr vs. n_elements
ax1 = axes[0, 0]
ax1.plot(results['n_elements'], results['f_cr'], 'o-', color='tab:blue', markersize=6)
ax1.axhline(y=FCR_ANA, color='red', linestyle='--', linewidth=1, label=f'$F_{{cr,ana}}$ = {FCR_ANA} kN')
ax1.set_xlabel('Número de Elementos')
ax1.set_ylabel('Fator de Carga Crítico [kN]')
ax1.set_title('Fator de Carga Crítico vs. Número de Elementos')
ax1.grid(True, alpha=0.3)
ax1.legend()
# Gráfico 2: f_cr vs. 1/n_elements
ax2 = axes[0, 1]
ax2.plot(results['inv_n_elements'], results['f_cr'], 'o-', color='tab:blue', markersize=6)
ax2.axhline(y=FCR_ANA, color='red', linestyle='--', linewidth=1, label=f'$F_{{cr,ana}}$ = {FCR_ANA} kN')
ax2.set_xlabel('1 / Número de Elementos')
ax2.set_ylabel('Fator de Carga Crítico [kN]')
ax2.set_title('Fator de Carga Crítico vs. 1 / Número de Elementos')
ax2.grid(True, alpha=0.3)
ax2.legend()
# Gráfico 3: Desvio Relativo vs. Tempo de Cálculo
ax3 = axes[1, 0]
ax3.plot(results['c_time_s'], results['rel_dev_f_cr_min'], 'o-', color='tab:blue', markersize=6)
ax3.set_xlabel('Tempo de Cálculo [s]')
ax3.set_ylabel('Desvio Relativo [%]')
ax3.set_title('Desvio Relativo vs. Tempo de Cálculo')
ax3.grid(True, alpha=0.3)
# Gráfico 4: Desvio Relativo vs. l_fe_mm
ax4 = axes[1, 1]
ax4.plot(results['l_fe_mm'], results['rel_dev_f_cr_min'], 'o-', color='tab:blue', markersize=6)
ax4.set_xlabel('Tamanho da Malha EF [mm]')
ax4.set_ylabel('Desvio Relativo [%]')
ax4.set_title('Desvio Relativo vs. Tamanho da Malha EF')
ax4.grid(True, alpha=0.3)
fig.suptitle('Análise de Convergência da Malha', fontsize=14, fontweight='bold')
fig.tight_layout()
fig.savefig('./ParaS-LBA_MeshConvergence-min_results.png', dpi=200)
plt.show()
A figura seguinte mostra os diagramas gerados pelo script do programa, tal como são também apresentados e avaliados com mais detalhe no artigo técnico mencionado no início.