Generalità
Gli studi parametrici sono un mezzo utile ed efficace per adattare le simulazioni. Gli obiettivi possono essere di diversa natura: trovare una struttura ottimale con peso minimo per garantire sicurezza e ridurre l'uso di risorse, adattare la finezza della mesh per ottenere risultati di simulazione affidabili o variare le proprietà fisiche per testare la sensibilità della simulazione e fare affermazioni affidabili sulla sicurezza strutturale. Per questi casi e molti altri, la modellazione parametrica è la soluzione ideale. Per non dover calcolare singolarmente tutte le varianti, è possibile utilizzare l'API di Dlubal. Ciò consente di eseguire il calcolo dello studio parametrico senza doverlo supervisionare.
Modellazione parametrica
Molte funzioni in RFEM 6, RSTAB 9 e RSECTION sono già parametrizzabili. Ulteriori informazioni sono reperibili nei relativi manuali:
- Manuali online RFEM 6 | Funzioni del programma | Input parametrico
- Manuali online RSTAB 9 | Funzioni del programma | Input parametrico
- Manuali online RSECTION 1 | Funzione del prodotto | Input parametrico
Accesso al modello tramite API
Per poter accedere al modello tramite API, è necessario installare il pacchetto corrispondente. Al posto di un editor esterno, è possibile utilizzare anche la console o lo Script manager direttamente nel programma principale. Maggiori informazioni sono disponibili ai seguenti link.
Esempio 1: Ottimizzazione dello spessore della parete
Questo modello consiste in una semplice scatola con un cilindro conico sovrapposto. Entrambe le parti sono costituite da piastra d'acciaio con diversi spessori di parete. Questi sono stati parametrizzati per condurre uno studio parametrico. L'obiettivo è trovare la configurazione la cui tensione equivalente secondo von Mises, con peso minimo, non superi la tensione di snervamento. Come carico agiscono il peso proprio e, in un caso di carico di carico utile, un carico concentrato per componente su una superficie rigida sulla parte superiore del cono. Il vincolo esterno è incernierato su due linee sul lato inferiore della scatola. Il modello e una vista dell'interfaccia utente con parametrizzazione sono mostrati di seguito.
Il seguente script è suddiviso in sezioni. Nella prima sezione vengono importate le librerie utilizzate, seguita dalla definizione delle variabili. Qui sono specificati la tensione di snervamento a scopo di visualizzazione e la campata dei parametri da variare, che sono collegati allo spessore della piastra. Successivamente vengono definite le funzioni che verranno utilizzate più avanti per creare la matrice delle varianti e modificare i parametri globali in RFEM 6. La parte principale del programma inizia con la creazione della matrice delle varianti dei parametri globali. A tale scopo, tutte le mutazioni possibili vengono generate dalle variazioni parametriche definite utilizzando il prodotto cartesiano. Successivamente, queste vengono ulteriormente limitate dalla condizione che lo spessore della parete nell'area inferiore debba essere maggiore o uguale a quello nella struttura superiore. Successivamente, viene stabilita la connessione con RFEM 6 tramite l'API di Dlubal. In questo caso, al modello attivo con la chiave API memorizzata nel file di configurazione. Entrambe le impostazioni possono anche essere specificate nella chiamata della funzione. Ora viene eseguito un ciclo, in cui uno dopo l'altro i parametri globali in RFEM 6 vengono adattati in base alla mutazione corrente, viene eseguito il calcolo e vengono letti i risultati. Per forzare un adattamento del modello e l'eliminazione dei risultati, la mesh viene eliminata e rigenerata prima del calcolo. Il massimo della tensione equivalente secondo von Mises di tutte le superfici viene determinato dai risultati della prima situazione di progetto. Il peso totale della struttura è determinato dalla forza risultante in direzione z nel caso di carico del peso proprio. Da questo, il peso viene calcolato dividendo per l'accelerazione gravitazionale. Nelle ultime due sezioni, i risultati vengono salvati come tabella Excel e tracciati come diagramma della tensione rispetto al peso della struttura.
"""ParaS-VMStress_Thick-min.py
Modello RFEM 6 correlato: "Para_Plate_Joint.rf6"
Modello: https://www.dlubal.com/en/downloads-and-information/examples-and-tutorials/models-to-download/006159
Esegue uno studio parametrico tramite l'API RFEM di Dlubal, raccoglie le metriche dei risultati, esporta e traccia i risultati.
e grafico.
Sezioni:
- Importazioni
- Configurazione
- Funzioni
- Esecuzione principale
- Tracciamento
"""
# --------------------- Importazioni ---------------------
import itertools
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from dlubal.api import rfem
# --------------------- Configurazione ---------------------
parameter_ranges = {
't_1': {'min': 8, 'max': 12, 'step': 2},
't_2': {'min': 4, 'max': 8, 'step': 2},
} # spessori in mm
f_y = 235 # tensione di snervamento in MPa
# --------------------- Funzioni ---------------------
def build_parameter_grid(param_ranges):
"""Costruisce un dataset di mutazioni completo da valori min/max/step in mm."""
columns = []
value_lists = []
for name, bounds in param_ranges.items():
min_val = bounds['min']
max_val = bounds['max']
step = bounds['step']
if step <= 0:
raise ValueError(f"L'ampiezza del passo per '{name}' deve essere positiva.")
if max_val < min_val:
raise ValueError(f"Il valore massimo per '{name}' deve essere >= del valore minimo.")
values = np.arange(min_val, max_val + step * 0.5, step)
columns.append(name)
value_lists.append(values)
combinations = list(itertools.product(*value_lists))
return pd.DataFrame(combinations, columns=columns)
def set_glpa(dl_app, p_name, p_value,):
"""Imposta il parametro globale p_name al valore specificato 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" Error: Parameter '{p_name}' not found!")
# --------------------- Esecuzione principale ---------------------
# Costruisce il set di parametri
para_dfo = build_parameter_grid(parameter_ranges)
para_dfo = para_dfo[para_dfo['t_1'] >= para_dfo['t_2']]
para_df = para_dfo / 1000 # mm -> m
print(f"Set di parametri:\n {para_dfo}")
# Connessione a RFEM con il modello corrente ed esecuzione dello studio parametrico
with rfem.Application() as rf_app:
# Verifica la connessione e stampa le informazioni sul modello
app_info = rf_app.get_application_info()
print("Informazioni applicazione:", app_info)
res_paras=pd.DataFrame(columns=['sigvm','sfz'])
for i in para_df.index:
# imposta i parametri globali
print(f"Imposta mutazione {i} con parametri {[x for x in para_df.loc[i]]}")
for p in para_df.columns:
# print(f"Imposta mutazione {i} con parametro {p} a {para_df.loc[i, p]}")
if not set_glpa(rf_app, p, para_df.loc[i, p]):
break
# Remesh
rf_app.delete_mesh()
rf_app.generate_mesh(skip_warnings=True)
# Esegue il calcolo
calculation = rf_app.calculate_all(skip_warnings=True)
# Risultati
if calculation.succeeded:
# Tensioni di 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 proprio
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 per accelerazione gravitazionale)
res_paras.loc[i] = pd.Series({'sigvm':sigvm,'sfz':sfz})
else:
print(f"Calcolo fallito per la mutazione {i} con parametri {[x for x in para_df.loc[i]]}")
res_paras.loc[i] = pd.Series({'sigvm':np.nan,'sfz':np.nan})
# Unisce parametri e risultati per l'esportazione e il tracciamento
para_out = pd.concat([para_dfo,res_paras], axis=1)
# ---------------------- Esporta Risultati ---------------------
para_out.to_excel('./ParaS-VMStress_Thick-min_results.xlsx')
# --------------------- Tracciamento ---------------------
fig, ax = plt.subplots(figsize=(12, 9))
ax.scatter(para_out['sfz'], para_out['sigvm'], color='tab:blue', s=50, label='Mutazioni')
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'Tensione di snervamento ({f_y} MPa)')
ax.set_title('Tensione vs. Massa per mutazioni parametriche')
ax.set_xlabel('Massa [kg]')
ax.set_ylabel('Tensione di von Mises [MPa]')
ax.legend()
fig.tight_layout()
fig.savefig('./ParaS-VMStress_Thick-min_results.png', dpi=200)
plt.show()
Come mostrato da questo semplice studio parametrico, rispetto alla configurazione iniziale con spessori di parete di 12 e 8 mm, con spessori di parete di 8 mm ciascuno, è possibile risparmiare circa il 30% di materiale senza superare la tensione di snervamento ammissibile. I risultati sono visibili nell'immagine seguente.
Esempio 2: Studio di convergenza della mesh
Questo esempio è già stato utilizzato per l'articolo tecnico sugli studi di convergenza della mesh. Si tratta di un guscio cilindrico per il quale viene eseguita un'analisi di buckling lineare. Il link all'articolo tecnico, che contiene una descrizione più dettagliata, e il modello corrispondente sono riportati di seguito:
L'obiettivo di uno studio di convergenza della mesh è adattare la finezza della mesh in modo che non si verifichino più variazioni rilevanti nei risultati, senza raggiungere un numero di elementi troppo elevato per consentire un lavoro economicamente vantaggioso. A tal fine, nel presente modello è stato introdotto un parametro globale (lfe). Questo è stato poi assegnato alla raffinatezza della mesh superficiale come dimensione della mesh.
Nello script seguente, lo studio di convergenza della mesh viene eseguito variando gradualmente le dimensioni della mesh FE target. La struttura di base corrisponde a quella del primo esempio, pertanto qui vengono discusse solo le differenze. Per analizzare l'influenza della finezza della mesh sul tempo di calcolo, questo viene determinato dal tempo di processo della chiamata di calcolo. I fattori di carico critico sono determinati dall'analisi di stabilità del primo caso di carico. Successivamente, viene determinata la variazione del fattore di carico rispetto al passo precedente per poter forzare un'interruzione in caso di un'ampia gamma di variazione. Prima dell'emissione dei risultati, viene eseguita un'analisi di convergenza che valuta la variazione relativa dei risultati.
"""ParaS-LBA_MeshConvergence-min.py
Modello RFEM 6 correlato: "MeshSensitivity-LBA_AlC.rf6"
Modello: https://www.dlubal.com/en/downloads-and-information/examples-and-tutorials/models-to-download/006080
Esegue uno studio di convergenza della mesh in RFEM variando il parametro globale
`l_fe`, raccoglie il fattore di carico critico, confronta le varianti, salva e traccia i risultati.
Sezioni:
- Importazioni
- Configurazione
- Esecuzione principale
- Tracciamento
"""
# --------------------- Importazioni ---------------------
# Libreria standard
import time
# Terze parti
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from dlubal.api import rfem
# ------------------- Configurazione -------------------
FE_SIZES_MM = [15, 12, 10, 9, 8, 7, 6, 5, 4, 3] # Elenco delle dimensioni degli elementi finiti da variare in millimetri
PARAM_NAME = "l_fe" # Nome del parametro globale nel modello
CONVERGENCE_THRESHOLD_PCT = 1.0 # Soglia di convergenza in percentuale
FCR_ANA = 1065 # Fattore di carico critico analitico (per riferimento nel grafico)
# ------------------- Funzioni -------------------
def set_glpa(dl_app, p_name, p_value,):
"""Imposta il parametro globale p_name al valore specificato 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" Error: Parameter '{p_name}' not found!")
# ------------------- Esecuzione principale -------------------
with rfem.Application() as rf_app:
# Verifica la connessione e stampa le informazioni sul modello
app_info = rf_app.get_application_info()
print("Informazioni applicazione:", app_info)
risultati = 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 # Converte mm in m per l'uso in RFEM
if not set_glpa(dl_app=rf_app, p_name=PARAM_NAME, p_value=l_fe_m):
break
# Remesh
rf_app.delete_mesh()
rf_app.generate_mesh(skip_warnings=True)
n_elem = rf_app.get_mesh_statistics().surface_2D_finite_elements
# Esegue il calcolo
t0 = time.perf_counter()
calculation = rf_app.calculate_all(skip_warnings=True)
t1 = time.perf_counter()
c_time = t1 - t0
# Risultati
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"Mutazione {i}: l_fe = {l_fe:.1f} mm | n_elements = {n_elem} | f_cr = {f_cr:.2f} | delta = {delta_str} %")
risultati.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"Calcolo fallito per la mutazione {i} con dimensione mesh {l_fe:.1f} mm")
risultati.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
})
# Calcola 1 / n_elements per il secondo grafico
risultati['inv_n_elements'] = 1.0 / risultati['n_elements']
# Calcola la deviazione relativa dal fattore di carico critico minimo
risultati['rel_dev_f_cr_min'] = (risultati['f_cr'] - risultati['f_cr'].min()) / risultati['f_cr'].min() * 100
# Analisi di convergenza
print(f"\n Analisi di convergenza (soglia: < {CONVERGENCE_THRESHOLD_PCT} % variazione):")
convergence_point = None
for (l_mm, n, a, d) in risultati.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" Convergenza raggiunta a l_fe = {convergence_point[0]:.1f} mm")
print(f" f_cr = {convergence_point[2]:.2f} | n_elements = {convergence_point[1]}")
else:
print(" Convergenza non ancora raggiunta – è necessaria una mesh più fine.")
# ---------------------- Esporta Risultati ---------------------
risultati.to_excel('./ParaS-LBA_MeshConvergence-min_results.xlsx')
# ------------------- Tracciamento -------------------
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Grafico 1: f_cr vs. n_elements
ax1 = axes[0, 0]
ax1.plot(risultati['n_elements'], risultati['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('Numero di Elementi')
ax1.set_ylabel('Fattore di Carico Critico [kN]')
ax1.set_title('Fattore di Carico Critico vs. Numero di Elementi')
ax1.grid(True, alpha=0.3)
ax1.legend()
# Grafico 2: f_cr vs. 1/n_elements
ax2 = axes[0, 1]
ax2.plot(risultati['inv_n_elements'], risultati['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 / Numero di Elementi')
ax2.set_ylabel('Fattore di Carico Critico [kN]')
ax2.set_title('Fattore di Carico Critico vs. 1 / Numero di Elementi')
ax2.grid(True, alpha=0.3)
ax2.legend()
# Grafico 3: Deviazione Relativa vs. Tempo di Calcolo
ax3 = axes[1, 0]
ax3.plot(risultati['c_time_s'], risultati['rel_dev_f_cr_min'], 'o-', color='tab:blue', markersize=6)
ax3.set_xlabel('Tempo di Calcolo [s]')
ax3.set_ylabel('Deviazione Relativa [%]')
ax3.set_title('Deviazione Relativa vs. Tempo di Calcolo')
ax3.grid(True, alpha=0.3)
# Grafico 4: Deviazione Relativa vs. l_fe_mm
ax4 = axes[1, 1]
ax4.plot(risultati['l_fe_mm'], risultati['rel_dev_f_cr_min'], 'o-', color='tab:blue', markersize=6)
ax4.set_xlabel('Dimensione Mesh FE [mm]')
ax4.set_ylabel('Deviazione Relativa [%]')
ax4.set_title('Deviazione Relativa vs. Dimensione Mesh FE')
ax4.grid(True, alpha=0.3)
fig.suptitle('Analisi di Convergenza della Mesh', fontsize=14, fontweight='bold')
fig.tight_layout()
fig.savefig('./ParaS-LBA_MeshConvergence-min_results.png', dpi=200)
plt.show()
L'immagine seguente mostra i diagrammi generati dallo script del programma, come descritti e valutati più dettagliatamente nell'articolo tecnico menzionato all'inizio.