227x
002055
2026-06-25

Parameterstudie mittels Dlubal API

В данной технической статье на двух примерах показано, как с помощью определения глобальных параметров и Dlubal API можно реализовать автоматизированное проведение параметрических исследований.

Общие сведения

Исследования параметров являются целесообразным и проверенным средством адаптации имитаций. Цели могут иметь различную природу: поиск оптимальной конструкции с минимальным весом для одновременного обеспечения надёжности и экономии источников, адаптация степени детализации сетки для получения надёжных результатов имитации или варьирование свойств материалов для проверки чувствительности имитации и получения надёжных выводов относительно надёжности конструкции. В этих и многих других случаях подходит параметрическое моделирование. Чтобы не пересчитывать все варианты по отдельности, можно использовать API Dlubal. Благодаря этому расчёт исследования параметров может выполняться без необходимости в надзоре.

Параметрическое моделирование

Многие функции в RFEM 6, RSTAB 9 и RSECTION уже могут быть параметризированы. Дополнительная информация содержится в соответствующих руководствах:

Доступ к модели через API

Чтобы получить доступ к модели через API, необходимо установить соответствующий пакет. Вместо внешнего редактора вы также можете напрямую использовать консоль или менеджер скриптов в главной программе. Дополнительная информация доступна по следующим ссылкам.

Пример 1: Оптимизация толщины стенки

Эта модель представляет собой простую коробку с установленным сверху коническим цилиндром. Обе части состоят из стального листа с различной толщиной стенки. Они были параметризированы для проведения исследования параметров. Целью является поиск конфигурации, при которой эквивалентное напряжение по фон Мизесу при минимальном весе не превышает предела текучести. В качестве нагрузки действуют собственный вес, а также в случае полезной нагрузки – покомпонентная сосредоточенная нагрузка на жёсткой поверхности в верхней части конуса. Опирание происходит шарнирно на две линии на нижней стороне коробки. Модель и вид пользовательского интерфейса с параметризацией можно увидеть ниже.

Последующий скрипт разделён на части. В первой части импортируются используемые библиотеки, за чем следует определение переменных. Здесь для целей визуализации указан предел текучести и диапазон варьируемых параметров, связанных с толщиной листа. Затем определяются функции, которые в дальнейшем будут использоваться для построения матрицы вариантов и изменения глобальных параметров в RFEM 6. Основная часть программы начинается с построения матрицы вариантов глобальных параметров. Для этого из заданных вариаций параметров с помощью декартова произведения создаются все возможные мутации. Затем они дополнительно ограничиваются условием, что толщина стенки в нижней части должна быть больше или равна толщине в верхней части. После этого устанавливается соединение с RFEM 6 через API Dlubal. В данном случае с активной моделью и ключом API, сохранённым в файле конфигурации. Обе настройки могут также задаваться непосредственно при вызове функции. Теперь выполняется цикл, в котором последовательно адаптируются глобальные параметры в RFEM 6 в соответствии с текущей мутацией, производится расчёт и считываются результаты. Чтобы гарантировать адаптацию модели и удаление результатов, перед расчётом сетка удаляется и создаётся заново. Максимальное эквивалентное напряжение по фон Мизесу по всем поверхностям определяется по результатам первой расчётной ситуации. Общий вес конструкции определяется по результирующей силе в направлении z в случае нагрузки от собственного веса. Из неё рассчитывается вес путём деления на ускорение свободного падения. В последних двух частях результаты сохраняются в виде таблицы Excel и отображаются на графике зависимости напряжения от веса конструкции.


"""ParaS-VMStress_Thick-min.py

Related RFEM 6 model: "Para_Plate_Joint.rf6"
Model: https://www.dlubal.com/en/downloads-and-information/examples-and-tutorials/models-to-download/006159

Run a parameter study via the Dlubal RFEM API, collect result metrics, export and plot results.
and chart.

Sections:
- Imports
- Configuration
- Functions
- Main execution
- Plotting
"""
# --------------------- Imports ---------------------
import itertools
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from dlubal.api import rfem


# --------------------- Configuration ---------------------
parameter_ranges = {
    't_1': {'min': 8, 'max': 12, 'step': 2},
    't_2': {'min': 4, 'max': 8, 'step': 2},
} # thicknesses in mm
f_y = 235  # yield strength in MPa

# --------------------- Functions ---------------------
def build_parameter_grid(param_ranges):
    """Build a complete mutation dataset from min/max/step values 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"Step width for '{name}' must be positive.")
        if max_val < min_val:
            raise ValueError(f"Max value for '{name}' must be >= min value.")
        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,):
    """Sets the global parameter p_name to the specified value 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!")

# --------------------- Main execution ---------------------
# Build parameter set
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"Parameter set:\n {para_dfo}")

# Connect to RFEM with current model and run parameter study
with rfem.Application() as rf_app:
    # Check connection and print model info
    app_info = rf_app.get_application_info()
    print("Application Info:", app_info)
    res_paras=pd.DataFrame(columns=['sigvm','sfz'])
    for i in para_df.index:
            # set global parameters
            print(f"Set mutation {i} with parameters {[x for x in para_df.loc[i]]}")
            for p in para_df.columns:
                # print(f"Set mutation {i} with parameter {p} to {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)
            # Run calculation
            calculation = rf_app.calculate_all(skip_warnings=True)
            # Results
            if calculation.succeeded:
                # Von Mises stresses
                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                
                # self-weight
                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 for gravity acceleration)

                res_paras.loc[i] = pd.Series({'sigvm':sigvm,'sfz':sfz})
            else:
                print(f"Calculation failed for mutation {i} with parameters {[x for x in para_df.loc[i]]}")
                res_paras.loc[i] = pd.Series({'sigvm':np.nan,'sfz':np.nan})

# Merge parameters and results for export and plotting
para_out = pd.concat([para_dfo,res_paras], axis=1)

# ---------------------- Export Results ---------------------
para_out.to_excel('./ParaS-VMStress_Thick-min_results.xlsx')

# --------------------- Plotting ---------------------
fig, ax = plt.subplots(figsize=(12, 9))
ax.scatter(para_out['sfz'], para_out['sigvm'], color='tab:blue', s=50, label='Mutations')
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'Yield Strength ({f_y} MPa)')
ax.set_title('Stress vs. Mass for Parameter Mutations')
ax.set_xlabel('Mass [kg]')
ax.set_ylabel('Von Mises Stress [MPa]')
ax.legend()
fig.tight_layout()
fig.savefig('./ParaS-VMStress_Thick-min_results.png', dpi=200)
plt.show()

Как показывает это простое исследование параметров, по сравнению с исходной конфигурацией с толщинами стенок 12 и 8 мм, при толщинах стенок по 8 мм можно сэкономить около 30 % материала, не превышая допустимого предела текучести. Результаты можно увидеть на следующем изображении.

Совет

Оптимизация путём уменьшения общей массы при соблюдении расчётных требований также возможна без каких-либо знаний в программировании с помощью аддона "Оптимизация модели" в сочетании с подходящим расчётным аддоном:

Пример 2: Исследование сходимости сетки

Этот пример уже использовался для специализированной статьи по исследованиям сходимости сетки. Речь идёт о цилиндрической оболочке, для которой проводится линейный расчёт на устойчивость. Ссылка на специализированную статью с более детальным описанием и соответствующая модель представлены ниже:

Цель исследования сходимости сетки состоит в адаптации степени детализации сетки таким образом, чтобы дальнейшие изменения результатов становились незначительными, не достигая при этом слишком большого количества элементов, что обеспечило бы экономичную работу. Для этого в существующей модели был введён глобальный параметр (lfe). Затем он был передан в параметр сгущения поверхностной сетки в качестве размера сетки.

Совет

Глобальные настройки сети также можно настраивать непосредственно через API без использования глобальных параметров:

В последующем скрипте исследование сходимости сетки проводится путём пошагового изменения целевого размера КЭ-сетки. По своей основной структуре оно соответствует первому примеру, поэтому здесь рассматриваются только отличия. Чтобы проанализировать влияние детализации сети на время расчёта, оно определяется по времени процесса вызова расчёта. Критические коэффициенты нагрузки определяются из расчёта на устойчивость для первого случая нагрузки. Затем производится определение изменения коэффициента нагрузки по сравнению с предыдущим шагом, чтобы при большей ширине вариации можно было принудительно остановить расчёт. Перед выводом результатов проводится анализ сходимости, который оценивает относительное изменение результатов.


"""ParaS-LBA_MeshConvergence-min.py

Related RFEM 6 model: "MeshSensitivity-LBA_AlC.rf6"
Model: https://www.dlubal.com/en/downloads-and-information/examples-and-tutorials/models-to-download/006080

Run a mesh convergence study in RFEM by sweeping the global parameter
`l_fe`, collect critical load factor, compare variants, save and plot results.

Sections:
- Imports
- Configuration
- Main execution
- Plotting
"""

# --------------------- Imports ---------------------
# Standard library
import time

# Third-party
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from dlubal.api import rfem

# ------------------- Configuration -------------------
FE_SIZES_MM = [15, 12, 10, 9, 8, 7, 6, 5, 4, 3] # List of finite element sizes to sweep in millimetres
PARAM_NAME = "l_fe"                             # Name of the global parameter in the model
CONVERGENCE_THRESHOLD_PCT = 1.0                 # Convergence threshold in percentage
FCR_ANA = 1065                                  # Analytical critical load factor (for reference in the plot)

# ------------------- Functions -------------------
def set_glpa(dl_app, p_name, p_value,):
    """Sets the global parameter p_name to the specified value 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!")
    
# ------------------- Main execution -------------------
with rfem.Application() as rf_app:
    # Check connection and print model info
    app_info = rf_app.get_application_info()
    print("Application Info:", 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  # Convert mm to m for use 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
        # Run calculation
        t0 = time.perf_counter()
        calculation = rf_app.calculate_all(skip_warnings=True)
        t1 = time.perf_counter()
        c_time = t1 - t0
        # Results
        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"Mutation {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"Calculation failed for mutation {i} with mesh size {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
                })

# Calculate 1 / n_elements for the second plot
results['inv_n_elements'] = 1.0 / results['n_elements']
# Calculate relative deviation from minimum critical load factor
results['rel_dev_f_cr_min'] = (results['f_cr'] - results['f_cr'].min()) / results['f_cr'].min() * 100

# Convergence analysis
print(f"\n  Convergence analysis (threshold: < {CONVERGENCE_THRESHOLD_PCT} % change):")
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"  Convergence reached at l_fe = {convergence_point[0]:.1f} mm")
    print(f"  f_cr = {convergence_point[2]:.2f}  |  n_elements = {convergence_point[1]}")
else:
    print("  Convergence not reached yet – finer mesh required.")

# ---------------------- Export Results ---------------------
results.to_excel('./ParaS-LBA_MeshConvergence-min_results.xlsx')

# ------------------- Plotting -------------------
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Plot 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('Number of Elements')
ax1.set_ylabel('Critical Load Factor [kN]')
ax1.set_title('Critical Load Factor vs. Number of Elements')
ax1.grid(True, alpha=0.3)
ax1.legend()

# Plot 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 / Number of Elements')
ax2.set_ylabel('Critical Load Factor [kN]')
ax2.set_title('Critical Load Factor vs. 1 / Number of Elements')
ax2.grid(True, alpha=0.3)
ax2.legend()

# Plot 3: Relative Deviation vs. Calculation Time
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('Calculation Time [s]')
ax3.set_ylabel('Relative Deviation [%]')
ax3.set_title('Relative Deviation vs. Calculation Time')
ax3.grid(True, alpha=0.3)

# Plot 4: Relative Deviation 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('FE Mesh Size [mm]')
ax4.set_ylabel('Relative Deviation [%]')
ax4.set_title('Relative Deviation vs. FE Mesh Size')
ax4.grid(True, alpha=0.3)

fig.suptitle('Mesh Convergence Analysis', fontsize=14, fontweight='bold')
fig.tight_layout()
fig.savefig('./ParaS-LBA_MeshConvergence-min_results.png', dpi=200)
plt.show()

На следующем рисунке показаны диаграммы, созданные скриптом программы, в том виде, в котором они также представлены и более детально проанализированы в упомянутой выше специализированной статье.


Автор

Марк работает в Product Engineering с акцентом на геотехнику и дополнительно оказывает поддержку в Customer Support. Свой профессиональный опыт он целенаправленно применяет при решении сложных вопросов.

Ссылки


;