Расчет критических скоростей вращения системы вал-ротор с учетом гироскопического эффекта во вращательной системе отсчета

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

Для корректной работы python-файла, сначало нужно установить библиотеки pyQt5=5.15.1 (именно 5.15.1, поскольку выше версии предпроцессор фидесиса не поддерживает), numpy, scipy, meshio, pyqtgraph: pip install pyQt5==5.15.1, pip install numpy, pip install scipy, pip install meshio, pip install pyqtgraph.

Создайте новый файл python. Для этого запустите среду разработки IDLE из меню «Пуск» или папки, где установлен Python. Выберите File - New File.

Cкопируйте в открывшееся пустое окно следующий скрипт:

import shutil,os
JOIN = os.path.join
# function clear working folder
def clear_working_folder(dir,file_name):  
    path_working_folder = os.listdir(dir)
    for element in path_working_folder:
        element_path =  JOIN(dir, element)   
        if element.startswith(file_name):
            if os.path.isfile(element_path):
                os.remove(element_path)
            if os.path.isdir(element_path):
                shutil.rmtree(element_path)
        else:
            continue 

Сохраните его с названием clear_working_folder.py

Затем создайте новый файл python в той же папке, где вы сохранили предыдущий файл:

# VM 247
import numpy as np
import os,sys,time
from clear_working_folder import JOIN 
FIDESYS_PREP_DIR = 'C:/Program Files/Fidesys/CAE-Fidesys-6.0/preprocessor/bin' 
prep_paths = [
FIDESYS_PREP_DIR,
JOIN(FIDESYS_PREP_DIR, 'plugins'),
JOIN(FIDESYS_PREP_DIR, 'acis', 'code', 'bin')
] # Preprocessor directory
calc_path = JOIN(FIDESYS_PREP_DIR, 'FidesysCalc.exe') # solution directory
for path in prep_paths:
    os.environ['PATH'] += os.pathsep + path # ADD directories to PATH.
    os.add_dll_directory(path)
    sys.path.append(path) # ADD directories to PATH.
try:
    import cubit, fidesys
except ModuleNotFoundError:
    print(f'Модули fydesys, cubit не найдены в директории {FIDESYS_PREP_DIR}')  
    sys.exit(1)

BASE_DIR = os.getcwd()
RESULT_FILENAME = 'modal'
modes_count = 6 
# diametres of circle sections   
section_diametr_array = [
    1.02E-2,
    2.04E-2,
    1.52E-2,
    4.06E-2,
    4.06E-2,
    6.6E-2,
    6.6E-2,
    5.08E-2,
    5.08E-2,
    2.54E-2,
    2.54E-2,
    3.04E-2,
    3.04E-2,
    2.54E-2,
    2.54E-2,
    7.62E-2,
    4.06E-2,
    4.06E-2
    ]
# block ids with pipe section
tube_section_ids = [7,8,18]

keypoints_beam_coords = [
    0,
    1.27E-2,
    5.08E-2,
    7.62E-2,
    8.89E-2,
    10.16E-2,
    10.67E-2,
    11.43E-2,
    12.7E-2,
    13.46E-2,
    16.51E-2,
    19.05E-2,
    22.86E-2,
    26.67E-2,
    28.7E-2,
    30.48E-2,
    31.5E-2,
    34.54E-2,
    35.5E-2
    ]
keypoints_spring_coords = [
    16.51E-2,16.51E-2,28.7E-2,28.7E-2
    ]
def create_beam_blocks():
    fidesys.cmd('create material 1')
    fidesys.cmd('modify material 1 name \'stal\'')
    fidesys.cmd('modify material 1 set property \'MODULUS\' value 2.078e+11')
    fidesys.cmd('modify material 1 set property \'SHEAR_MODULUS\' value 1e+12')
    fidesys.cmd('modify material 1 set property \'DENSITY\' value 7806')
    for keypoint_coord in keypoints_beam_coords:
        fidesys.cmd(f'create vertex {keypoint_coord} 0 0')
    for keypoint_id in range(1,len(keypoints_beam_coords)+1):
        if keypoint_id < len(keypoints_beam_coords):
            fidesys.cmd(f'create curve vertex {keypoint_id} {keypoint_id+1}')
        else:
            break
    fidesys.cmd('merge all')
    fidesys.cmd('curve all interval 1')
    fidesys.cmd('curve all scheme equal')
    fidesys.cmd('mesh curve all')
    fidesys.cmd('merge all')
    for index in range(len(section_diametr_array)):
        diametr = section_diametr_array[index]
        fidesys.cmd(f'block {index + 1} add curve {index + 1}')
        fidesys.cmd(f'create beam properties {index + 1}')
        fidesys.cmd(f'modify beam properties {index + 1} type \'Ellipse\'')
        fidesys.cmd(f'modify beam properties {index + 1} angle 0.0')
        fidesys.cmd(f'modify beam properties {index + 1} ey 0.0')
        fidesys.cmd(f'modify beam properties {index + 1} ez 0.0')
        fidesys.cmd(f'modify beam properties {index + 1} geom_a {diametr}')
        fidesys.cmd(f'modify beam properties {index + 1} geom_b {diametr}')
        fidesys.cmd(f'modify beam properties {index + 1} mesh_quality 6')
        fidesys.cmd(f'modify beam properties {index + 1} warping_dof off')
        fidesys.cmd(f'block {index + 1} material 1 cs 1 element beam order 1')
        fidesys.cmd(f'block {index + 1} beam properties {index + 1}')
    # modify pipe sections
    for tube_section_id in tube_section_ids:
        internal_diametr = 0.0304
        fidesys.cmd(f'modify beam properties {tube_section_id} type \'Circle With Offset Hole\'')
        fidesys.cmd(f'modify beam properties {tube_section_id} angle 0')
        fidesys.cmd(f'modify beam properties {tube_section_id} ey 0')
        fidesys.cmd(f'modify beam properties {tube_section_id} ez 0')
        fidesys.cmd(f'modify beam properties {tube_section_id} geom_d1 {section_diametr_array[tube_section_id - 1]}')
        internal_diametr = 0.0356  if tube_section_id == 8 else  internal_diametr
        fidesys.cmd(f'modify beam properties {tube_section_id} geom_d2 {internal_diametr}') 
        fidesys.cmd(f'modify beam properties {tube_section_id} geom_e 0')
        fidesys.cmd(f'modify beam properties {tube_section_id} mesh_quality 6')
        fidesys.cmd(f'modify beam properties {tube_section_id} warping_dof off')  
    fidesys.cmd('create displacement  on vertex all dof 1 dof 4 fix 0') 
    
def create_combin_blocks():
    increment = 0 
    length = len(keypoints_beam_coords)    
    for keypoints_spring_coord_index  in range(len(keypoints_spring_coords)):
        if keypoints_spring_coord_index % 2 == 0:
            fidesys.cmd(f'create curve location {keypoints_spring_coords[keypoints_spring_coord_index]} 0 0 location {keypoints_spring_coords[keypoints_spring_coord_index]} 1e-03 0')
        else:
            fidesys.cmd(f'create curve location {keypoints_spring_coords[keypoints_spring_coord_index]} 0 0 location {keypoints_spring_coords[keypoints_spring_coord_index]} 0 1e-03')
        increment += 1    
        combin_line_id = length - 1 + increment
        fidesys.cmd('curve all interval 1')
        fidesys.cmd('curve all scheme equal')
        fidesys.cmd(f'mesh curve {combin_line_id}')
        fidesys.cmd(f'block {length } add curve {combin_line_id}')
    fidesys.cmd('merge all')
    fidesys.cmd('create spring properties 1')
    fidesys.cmd('modify spring properties 1 type \'linear_spring\'')
    fidesys.cmd('modify spring properties 1 stiffness 4.37e7')
    fidesys.cmd('modify spring properties 1 spring_constant_damping 0')
    fidesys.cmd('modify spring properties 1 spring_linear_damping 0')
    fidesys.cmd('modify spring properties 1 spring_mass 0')
    fidesys.cmd(f'block {length} element spring')
    fidesys.cmd(f'block {length} spring properties 1')
    fidesys.cmd('create displacement  on vertex 42 38 44 40  dof all fix')

def create_point_mass_block():
    length = len(keypoints_beam_coords) 
    fidesys.cmd(f'block { length + 1 } add vertex 5')
    fidesys.cmd('create lumpmass properties 1')
    fidesys.cmd('modify lumpmass properties 1 mass_x 1.401')
    fidesys.cmd('modify lumpmass properties 1 mass_y 1.401')
    fidesys.cmd('modify lumpmass properties 1 mass_z 1.401')
    fidesys.cmd('modify lumpmass properties 1 mass_inertia_x 0.002')
    fidesys.cmd('modify lumpmass properties 1 mass_inertia_y 0.00136')
    fidesys.cmd('modify lumpmass properties 1 mass_inertia_z 0.00136')
    fidesys.cmd(f'block  { length + 1 } cs 1 element lumpmass')
    fidesys.cmd(f'block { length + 1 } lumpmass properties 1')

def calculate(omega):
    modal_result_path = f'{BASE_DIR}/{RESULT_FILENAME}_{round(omega/0.1047,3) }'
    cubit.init([''])
    fc = fidesys.FidesysComponent()
    fc.init_application(FIDESYS_PREP_DIR) # initialization
    fc.start_up_no_args() # run fidesys component fc
    fidesys.cmd('reset')
    create_beam_blocks()
    create_combin_blocks()
    create_point_mass_block()
    fidesys.cmd('create angular velocity global')
    fidesys.cmd(f'modify angular velocity 1 dof 1 value {omega}')
    fidesys.cmd('analysis type eigenfrequencies dim3 preload off')
    fidesys.cmd(f'eigenvalue find {modes_count} smallest')
    fidesys.cmd('damping off mass_matrix 0 stiffness_matrix 0')
    fidesys.cmd('coriolis on')  
    fidesys.cmd(f"calculation start path '{modal_result_path}.pvd'")
    fc.delete_application()

Сохраните файл с названием calculate_modal.py

Еще раз создайте новый файл python в той же папке, где вы сохранили предыдущий файл:

from PyQt5 import  QtCore,QtTest
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QRegExpValidator
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QTableWidget,QTableWidgetItem,QWidget,QGridLayout,QLineEdit,QLabel,QMessageBox,QPlainTextEdit
import sys,os,meshio
import numpy as np
import pyqtgraph as pg
from clear_working_folder import  clear_working_folder 
from calculate_modal import modes_count,BASE_DIR,RESULT_FILENAME,calculate
from scipy.interpolate import interp1d
from scipy.linalg import solve
from scipy.optimize import root
from random import randrange

class Window(QMainWindow):
    rotational_velocities_rpm = np.array([0,35000,70000,105000])
    rotational_velocities_radian = rotational_velocities_rpm * 0.1047
    rotational_velocities_hz = rotational_velocities_rpm * 0.017
    def __init__(self):
        super().__init__()
        self.setupUi() # Create Widgets

    def setupUi(self):
        self.setWindowTitle('Campell diagram')
        self.setFixedHeight(1000)
        self.setFixedWidth(1000)
        self.pre_processor_title_create()
        self.btn_calculate_create()
        self.table_list_rotational_velocities_create()
        self.post_processor_title_create()
        self.graph_Widget_create()
        self.table_list_critical_speeds_create()
        self.text_box_ratio_edit_create()
        self.text_title_ratio_create()
        self.button_replot_graph_and_recalculate_critical_speeds_create()
        self.terminal_create()
        self.toggle_visibility_widgets()

    # empty Function
    def empty_function(self,omega):
        pass   

    # BLOCK OF CREATING WIDGETS BEGINS!!
    # create table list of rotational velocities
    def table_list_rotational_velocities_create(self):
        length = len(self.rotational_velocities_rpm)
        self.table_list_rotational_velocities = QTableWidget(self) 
        self.table_list_rotational_velocities.setGeometry(50,50,220,120)
        self.table_list_rotational_velocities.setRowCount(length)
        self.table_list_rotational_velocities.setColumnCount(1)
        self.table_list_rotational_velocities.setHorizontalHeaderLabels(['Rotational velocity (rpm)'])  
        self.table_list_rotational_velocities.horizontalHeader().setDefaultSectionSize(200)
        self.table_list_rotational_velocities.verticalHeader().setDefaultSectionSize(15)
        self.insert_rotational_velocities_values_into_table()

    #create preprocessor title    
    def pre_processor_title_create(self):
        self.pre_processor_title =  QLabel(self)
        self.pre_processor_title.setText('PREPROCESSOR (4 VELOCITIES -> 4 STEPS)')
        self.pre_processor_title.setStyleSheet('font-size:30px')
        self.pre_processor_title.setGeometry(200, 0, 700, 40)

    # create button calculate
    def btn_calculate_create(self):
        self.btn_calculate = QPushButton('Calculate fidesys script',self)
        self.btn_calculate.move(50,350)
        self.btn_calculate.setGeometry(500, 80, 200, 40) 
        self.btn_calculate.clicked.connect(self.btn_calculate_handler)

    # create postprocessor title
    def post_processor_title_create(self):
        self.post_processor_title =  QLabel(self)
        self.post_processor_title.setText('POSTPROCESSOR')
        self.post_processor_title.setStyleSheet('font-size:30px')
        self.post_processor_title.setGeometry(400, 380, 300, 40) 

    # create graph widget
    def graph_Widget_create(self):               
        self.plot_graph = pg.PlotWidget()
        self.plot_graph.setBackground('w')
        self.plot_graph.addLegend(offset=(-650, 5))
        self.plot_graph.setStyleSheet("border : none;") 
        self.widget = QWidget(self)
        self.layout = QGridLayout()
        self.layout.addWidget(self.plot_graph)
        self.widget.setLayout(self.layout)
        self.widget.setStyleSheet("border : 5px solid blue;") 
        self.widget.setGeometry(100,420, 800, 400)

    # create table list of critical speeds
    def table_list_critical_speeds_create(self):
        self.table_list_critical_speeds = QTableWidget(self)
        self.table_list_critical_speeds.setGeometry(50,850,220,120)
        self.table_list_critical_speeds.setColumnCount(1)
        self.table_list_critical_speeds.setHorizontalHeaderLabels(['Critical speeds (rpm)'])  
        self.table_list_critical_speeds.horizontalHeader().setDefaultSectionSize(200)
        self.table_list_critical_speeds.verticalHeader().setDefaultSectionSize(15)

    # create text box edit ratio of critical line
    def text_box_ratio_edit_create(self):        
        self.text_box_ratio_edit = QLineEdit(self)
        self.text_box_ratio_edit.setGeometry(450,925,50,20)    
        self.text_box_ratio_edit.setAlignment(QtCore.Qt.AlignCenter) 
        validator = QRegExpValidator(QRegExp("\d+\.?\d*e?-?\d+"))
        self.text_box_ratio_edit.setValidator(validator)
        self.text_box_ratio_edit.setText('1') 

    # create button replot campell's diagram and recalculate critical speeds
    def button_replot_graph_and_recalculate_critical_speeds_create(self):
        self.button_replot_graph_and_recalculate_critical_speeds = QPushButton('Replot',self)
        self.button_replot_graph_and_recalculate_critical_speeds.setGeometry(550,920,150,30)
        self.button_replot_graph_and_recalculate_critical_speeds.clicked.connect(self.button_replot_graph_and_recalculate_critical_speeds_handler)
    
    # create text title 'RATIO' 
    def text_title_ratio_create(self):
        self.text_title_ratio = QLabel(self)
        self.text_title_ratio.setGeometry(400,925,50,20)
        self.text_title_ratio.setText('RATIO') 

    #  create terminal   
    def terminal_create(self):
        self.terminal =  QPlainTextEdit(self) 
        self.terminal.setGeometry(100,180, 800, 200) 
    # BLOCK OF CREATING WIDGETS FINISH!!  
                                     
    # control visibility of Widgets
    def toggle_visibility_widgets(self,is_visible = False):
            self.button_replot_graph_and_recalculate_critical_speeds.setVisible(is_visible)
            self.text_box_ratio_edit.setVisible(is_visible)
            self.text_title_ratio.setVisible(is_visible)
            self.table_list_critical_speeds.setVisible(is_visible)

    # plot diagram campell 
    def plot_campell_diagramm(self,modes_dict,ratio = 1):
        def create_random_color(): # random hex color
            symbols = [1,2,3,4,5,6,7,8,9,'A','B','C','D','E']
            color = '#'
            for i in range(0,6):
                random_index = randrange(0,len(symbols))
                random_symbol = symbols[random_index]
                color = color + str(random_symbol)
            return color
        self.plot_graph.clear()
        for mode_index in range(1,modes_count+1):
            line_color = create_random_color()    
            self.plot_graph.plot(self.rotational_velocities_rpm,modes_dict[f'{mode_index} mode'], fillLevel = 10,pen = pg.mkPen(color = line_color,width = 3),name = f'Mode_{mode_index}')
        self.plot_graph.plot(self.rotational_velocities_rpm,self.rotational_velocities_hz/float(ratio),pen = pg.mkPen(color = "black",width = 4))
        
    # insert_rotational_velocities_values_into_table    
    def insert_rotational_velocities_values_into_table(self):
        table = self.table_list_rotational_velocities
        length = len(self.rotational_velocities_rpm)
        for index in range(length):
            table.setItem(index,0,QTableWidgetItem(str(self.rotational_velocities_rpm[index])))
            table.item(index,0).setFlags(QtCore.Qt.ItemIsEditable)
            table.item(index,0).setTextAlignment(QtCore.Qt.AlignHCenter) 

    # get damping frequencies from vtu                                                                                                       
    def get_frequencies_from_vtu(self,calculate_fidesys_script_function):
        modes_dict = {}
        modes_dict_for_plotting = {}    
        for omega in self.rotational_velocities_radian:
            modal_result_path_result_vtu = f'{BASE_DIR}/{RESULT_FILENAME}_{round(omega/0.1047,3)}/case1_step0001_substep0001.vtu'
            calculate_fidesys_script_function(omega)
            try:
                mesh = meshio.read(f'{modal_result_path_result_vtu}')
            except meshio._exceptions.ReadError:
                self.terminal.insertPlainText(f'Error!!!.Vtu file from {modal_result_path_result_vtu} is not Found\n')
                clear_working_folder(BASE_DIR,RESULT_FILENAME)             
                self.btn_calculate.setEnabled(True)    
                raise meshio._exceptions.ReadError('Ошибка! Vtu-Файл не найден!')
            damping_freqs = mesh.field_data['Eigen Values Extended']
            damping_freqs = list(damping_freqs[0])
            modes_dict[f'{omega} rad/s'] = damping_freqs
        for mode_index in range(1,modes_count+1):
            modes_dict_for_plotting[f'{mode_index} mode'] = []
            for key in modes_dict:
                element = round(modes_dict[key][mode_index-1],3)
                modes_dict_for_plotting[f'{mode_index} mode'].append(element) 
        for key,value in  modes_dict_for_plotting.items():
            modes_dict_for_plotting[key] = np.array(value)        
        return modes_dict_for_plotting
    
    # calculate damping Frequencies
    def calculate_damping_frequencies(self,omega):
        self.terminal.insertPlainText(f'Calculation with {round(omega/0.1047,3)} rpm begin\n')
        calculate(omega)
        QtTest.QTest.qWait(3000)
        self.terminal.insertPlainText(f'Calculation with {round(omega/0.1047,3)} rpm finished succesfully\n')
   
    # calculate critical speeds 
    def critital_speeds_calculate(self,modes_dict,ratio = 1):
        self.toggle_visibility_widgets(True)
        critical_line_matrix = np.array([[self.rotational_velocities_rpm[0],1],[self.rotational_velocities_rpm[-1],1]])
        critical_line_vector = np.array([self.rotational_velocities_hz[0]/float(ratio),self.rotational_velocities_hz[-1]/float(ratio)])
        [k1,b1] = solve(critical_line_matrix,critical_line_vector) # equation of line y = k1*x+ b1. Find coeffs k1,b1
        def intersect_function(x):    #  point of intersection of two linear functions
            return  k*x + b - k1*x - b1    
        critical_speeds = []
        for key in modes_dict:
            array = modes_dict[key]
            for index in range(0,len(array)-1):
                current_rotational_velocity =  self.rotational_velocities_rpm[index]
                next_rotational_velocity = self.rotational_velocities_rpm[index + 1]
                current_speed = array[index] 
                next_speed = array[index + 1]
                interval_rotational_velocity = np.array([current_rotational_velocity,next_rotational_velocity])
                interval_speed = np.array([current_speed,next_speed])
                linear_interpolation =interp1d(interval_rotational_velocity,interval_speed,kind = 'linear')
                matrix = np.array([[current_rotational_velocity,1],[next_rotational_velocity,1]])
                vector = np.array([current_speed,next_speed])
                [k,b] = solve(matrix,vector) # equation of line y = k*x+b. Find coeffs k,b
                sol = root(intersect_function, x0=0) # find intersect of two functions in current_interval
                if current_rotational_velocity <= sol.x[0] <= next_rotational_velocity:  
                    critical_speed = round(float(linear_interpolation(sol.x[0])),3)
                    critical_speeds.append(critical_speed)                 
                else:
                    continue
        critical_speeds = np.array(critical_speeds)/0.017 # critical speeds in rpm
        return np.round(critical_speeds,3) 
    
    # clear critical speeds table
    def table_list_critical_speeds_clear(self):
         table = self.table_list_critical_speeds
         while (table.rowCount() > 0):
              table.removeRow(0)

    # insert critical speeds into table          
    def insert_critical_speeds_into_table(self,modes_dict,ratio = 1):
        self.table_list_critical_speeds_clear()
        table = self.table_list_critical_speeds
        critical_speeds =  self.critital_speeds_calculate(modes_dict,ratio) 
        length = len(critical_speeds)
        table.setRowCount(length)
        for index in range(length):
           table.setItem(index,0,QTableWidgetItem(str(critical_speeds[index])))
           table.item(index,0).setFlags(QtCore.Qt.ItemIsEditable)
           table.item(index,0).setTextAlignment(QtCore.Qt.AlignHCenter)            

    # BLOCK OF CREATING HANDLERS BUTTONS BEGIN!!
    # create button calculate handler
    def btn_calculate_handler(self):
        self.terminal.clear()
        os.system('cls')
        self.btn_calculate.setEnabled(False)
        self.toggle_visibility_widgets(False)
        self.text_box_ratio_edit.setText('1') 
        self.plot_graph.clear()
        QtTest.QTest.qWait(2000)            
        self.terminal.insertPlainText('Calculation start\n')
        QtTest.QTest.qWait(2000)
        self.terminal.insertPlainText('Clear Working Folder\n')
        clear_working_folder(BASE_DIR,RESULT_FILENAME)
        QtTest.QTest.qWait(3000) 
        self.terminal.insertPlainText('Clear Working Folder finished\n')   
        modes_dict =  self.get_frequencies_from_vtu(self.calculate_damping_frequencies)      
        self.plot_campell_diagramm(modes_dict)
        self.insert_critical_speeds_into_table(modes_dict)
        self.btn_calculate.setEnabled(True)
        self.terminal.insertPlainText('Calculation finished\n') 

    # create button replot_graph_and_recalculate_critical_speeds handler
    def button_replot_graph_and_recalculate_critical_speeds_handler(self):
        try:
           self.terminal.insertPlainText(f'plot and calculate critical speeds with ratio {self.text_box_ratio_edit.text()} of critical line \n')   
           self.plot_graph.clear()
           modes_dict =  self.get_frequencies_from_vtu(self.empty_function)
           self.plot_campell_diagramm(modes_dict,float(self.text_box_ratio_edit.text()))
           self.insert_critical_speeds_into_table(modes_dict,float(self.text_box_ratio_edit.text()))
        except  ValueError:       
            self.message_box = QMessageBox(self)
            self.message_box.setText('Invalid ratio')
            self.message_box.exec_()
    # BLOCK OF CREATING HANDLERS BUTTONS END!!                            

def application():
    app = QApplication(sys.argv)
    window = Window()
    window.show()  
    sys.exit(app.exec_())

if __name__ == '__main__':
    application()    

Сохраните его с названием Window.py

Запустите на расчет файл Window.py. Выберите Run - Run Module.

После того как расчет прошел появится диалоговое окно для расчета:

Нажмите на Calculate fidesys script. В результате построится диаграмма Кемпбелла и посчитаются критические скорости для коэффициента критической линии 1:

В поле RATIO можно поменять коэффициент критической линии и диаграмма Кэмпбелла с табличкой расчета критических скоростей перерасчитается заново (с помощью кнопки Replot).