Документация Engee
Notebook

Исследование крутильных колебаний многомассовой системы

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

Крутильные колебания возникают в системах с несколькими инерционными массами, соединенными упругими валами. Каждая такая система имеет набор собственных частот (мод), на которых происходит резонансное усиление колебаний.

Начальное возбуждение: импульсный момент 1000 Н⋅м длительностью 0.01 с.

Схема модели:

Для реализации модели крутильных колебаний используется многомассовая система с упругими связями.

torsional_oscillations--1759313337141.png

Определение функции для загрузки и запуска модели:

In [ ]:
m = "torsional_oscillations"
function start_model_engee(m)
    try
        engee.close(m, force=true) # закрытие модели если открыта
    catch err # в случае, если нет модели, которую нужно закрыть
        # модель не была открыта
    end
    
    try
        m = engee.load("$((@__DIR__))/$m.engee") # загрузка модели
        engee.run(m) # запуск модели
    catch err # в случае, если модель не загружена
        m = engee.load("$((@__DIR__))/$m.engee") # загрузка модели
        engee.run(m) # запуск модели
    end
end
Out[0]:
start_model_engee (generic function with 1 method)

Определение функций для анализа:

Функция FFT анализа собственных частот:

In [ ]:
import Pkg; 
Pkg.add(["DSP", "Peaks"])
In [ ]:
using FFTW, DSP

function analyze_natural_frequencies(time_data, speed_data)
    # Подготовка данных для FFT
    dt = time_data[2] - time_data[1]
    fs = 1/dt  # Частота дискретизации
    
    # Применение оконной функции для лучшего разрешения
    windowed_data = speed_data .* hanning(length(speed_data))
    
    # Вычисление FFT
    fft_result = fft(windowed_data)
    
    # Частотная ось
    frequencies = fftfreq(length(fft_result), fs)
    
    # Амплитудный спектр в dB
    magnitude_db = 20 * log10.(abs.(fft_result) .+ 1e-10)
    
    return frequencies, magnitude_db
end
Out[0]:
analyze_natural_frequencies (generic function with 1 method)

Функция поиска пиков собственных частот:

In [ ]:
using Peaks

function find_natural_frequencies(frequencies, magnitude_db, min_height=20)
    # Поиск пиков в положительной части спектра  
    pos_freq_idx = frequencies .> 0
    pos_freqs = frequencies[pos_freq_idx]
    pos_magnitudes = magnitude_db[pos_freq_idx]
    
    # Поиск локальных максимумов
    peaks, _ = findmaxima(pos_magnitudes)
    
    # Фильтрация по высоте
    high_peaks = peaks[pos_magnitudes[peaks] .> min_height]
    
    natural_freqs = pos_freqs[high_peaks]
    amplitudes = pos_magnitudes[high_peaks]
    
    return natural_freqs, amplitudes
end
[ Info: Precompiling MakieExt [c3e534c0-8be5-5387-af52-275fbf2a048d] 
GKS: cannot open display - headless operation mode active
GKS: could not find font bold.ttf
WARNING: could not import Makie.documented_attributes into MakieExt
ERROR: LoadError: UndefVarError: `documented_attributes` not defined in `MakieExt`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ /usr/local/ijulia-core/packages/MakieCore/hXATT/src/recipes.jl:286
 [2] include
   @ ./Base.jl:557 [inlined]
 [3] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::Nothing)
   @ Base ./loading.jl:2881
 [4] top-level scope
   @ stdin:6
in expression starting at /usr/local/ijulia-demos/packages/Peaks/frdrm/ext/MakieExt.jl:1
in expression starting at stdin:6
Error: Error during loading of extension MakieExt of Peaks, use `Base.retry_load_extensions()` to retry.
  exception =
   1-element ExceptionStack:
   Failed to precompile MakieExt [c3e534c0-8be5-5387-af52-275fbf2a048d] to "/user/.packages/compiled/v1.11/MakieExt/jl_L1Hxn7".
   Stacktrace:
     [1] error(s::String)
       @ Base ./error.jl:35
     [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool; flags::Cmd, cacheflags::Base.CacheFlags, reasons::Dict{String, Int64}, loadable_exts::Vector{Base.PkgId})
       @ Base ./loading.jl:3174
     [3] (::Base.var"#1110#1111"{Base.PkgId})()
       @ Base ./loading.jl:2579
     [4] mkpidlock(f::Base.var"#1110#1111"{Base.PkgId}, at::String, pid::Int32; kwopts::@Kwargs{stale_age::Int64, wait::Bool})
       @ FileWatching.Pidfile /usr/local/julia/share/julia/stdlib/v1.11/FileWatching/src/pidfile.jl:95
     [5] #mkpidlock#6
       @ /usr/local/julia/share/julia/stdlib/v1.11/FileWatching/src/pidfile.jl:90 [inlined]
     [6] trymkpidlock(::Function, ::Vararg{Any}; kwargs::@Kwargs{stale_age::Int64})
       @ FileWatching.Pidfile /usr/local/julia/share/julia/stdlib/v1.11/FileWatching/src/pidfile.jl:116
     [7] #invokelatest#2
       @ ./essentials.jl:1057 [inlined]
     [8] invokelatest
       @ ./essentials.jl:1052 [inlined]
     [9] maybe_cachefile_lock(f::Base.var"#1110#1111"{Base.PkgId}, pkg::Base.PkgId, srcpath::String; stale_age::Int64)
       @ Base ./loading.jl:3698
    [10] maybe_cachefile_lock
       @ ./loading.jl:3695 [inlined]
    [11] _require(pkg::Base.PkgId, env::Nothing)
       @ Base ./loading.jl:2565
    [12] __require_prelocked(uuidkey::Base.PkgId, env::Nothing)
       @ Base ./loading.jl:2388
    [13] #invoke_in_world#3
       @ ./essentials.jl:1089 [inlined]
    [14] invoke_in_world
       @ ./essentials.jl:1086 [inlined]
    [15] _require_prelocked
       @ ./loading.jl:2375 [inlined]
    [16] _require_prelocked
       @ ./loading.jl:2374 [inlined]
    [17] run_extension_callbacks(extid::Base.ExtensionId)
       @ Base ./loading.jl:1544
    [18] run_extension_callbacks(pkgid::Base.PkgId)
       @ Base ./loading.jl:1576
    [19] run_package_callbacks(modkey::Base.PkgId)
       @ Base ./loading.jl:1396
    [20] __require_prelocked(uuidkey::Base.PkgId, env::String)
       @ Base ./loading.jl:2399
    [21] #invoke_in_world#3
       @ ./essentials.jl:1089 [inlined]
    [22] invoke_in_world
       @ ./essentials.jl:1086 [inlined]
    [23] _require_prelocked
       @ ./loading.jl:2375 [inlined]
    [24] macro expansion
       @ ./loading.jl:2314 [inlined]
    [25] macro expansion
       @ ./lock.jl:273 [inlined]
    [26] __require(into::Module, mod::Symbol)
       @ Base ./loading.jl:2271
    [27] #invoke_in_world#3
       @ ./essentials.jl:1089 [inlined]
    [28] invoke_in_world
       @ ./essentials.jl:1086 [inlined]
    [29] require(into::Module, mod::Symbol)
       @ Base ./loading.jl:2260
    [30] eval
       @ ./boot.jl:430 [inlined]
    [31] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
       @ Base ./loading.jl:2734
    [32] softscope_include_string(m::Module, code::String, filename::String)
       @ SoftGlobalScope /usr/local/ijulia-core/packages/SoftGlobalScope/u4UzH/src/SoftGlobalScope.jl:65
    [33] execute_request(socket::ZMQ.Socket, msg::IJulia.Msg)
       @ IJulia /usr/local/ijulia-core/packages/IJulia/czQmB/src/execute_request.jl:86
    [34] #invokelatest#2
       @ ./essentials.jl:1055 [inlined]
    [35] invokelatest
       @ ./essentials.jl:1052 [inlined]
    [36] eventloop(socket::ZMQ.Socket)
       @ IJulia /usr/local/ijulia-core/packages/IJulia/czQmB/src/eventloop.jl:8
    [37] (::IJulia.var"#15#17")()
       @ IJulia /usr/local/ijulia-core/packages/IJulia/czQmB/src/eventloop.jl:38
@ Base loading.jl:1550
Out[0]:
find_natural_frequencies (generic function with 2 methods)

Запуск симуляции с импульсным воздействием

In [ ]:
start_model_engee(m);

Запустив модель в среде моделирования, в окне "Визуализация сигналов" можно увидеть график сигнала скорости после БПФ (быстрого преобразования Фурье) в частотной области:

newplot - 2025-10-01T115035.780.png

Проанализировав этот график можно обнаружить собственные частоты системы.

Построим аналогичный график в данном интерактивном скрипте, для этого необходимо вывести данные симуляции в переменные.

Вывод данных для анализа:

In [ ]:
# Получение угловых скоростей всех инерций
omega1 = collect(simout["torsional_oscillations/FFT по скорости.1"]);

# Выделение времени и данных для анализа
time_data = omega1[:,1];
speed_data = omega1[:,2];  # Анализируем первую инерцию

Выполнение FFT анализа:

In [ ]:
# FFT анализ для определения собственных частот
frequencies, magnitude_db = analyze_natural_frequencies(time_data, speed_data);
natural_freqs, amplitudes = find_natural_frequencies(frequencies, magnitude_db);

# Вывод найденных собственных частот
println("Обнаруженные собственные частоты:")
for (i, (freq, amp)) in enumerate(zip(natural_freqs, amplitudes))
    println("$(i)-я мода: $(round(freq, digits=2)) Гц ($(round(amp .-30, digits=2)) dBm)")
end
Обнаруженные собственные частоты:
1-я мода: 54.16 Гц (63.55 dBm)
2-я мода: 102.51 Гц (25.96 dBm)
3-я мода: 141.2 Гц (30.16 dBm)
4-я мода: 165.7 Гц (11.16 dBm)

Визуализация проведённого анализа:

In [ ]:
using Plots
using Printf

function create_torsional_analysis_plots(time_data, speed_data, frequencies, magnitude_db)
    gr()
    
    # Преобразуем все данные в вещественные числа
    real_frequencies = real.(frequencies)
    real_magnitude_db = real.(magnitude_db)
    real_time_data = real.(time_data)
    real_speed_data = real.(speed_data)
    
    # График временного отклика
    p1 = plot(real_time_data, real_speed_data, 
              title="Крутильные колебания после импульса",
              xlabel="Время, с", 
              ylabel="Угловая скорость, рад/с",
              linewidth=2,
              linecolor=:blue)
    
    # График FFT спектра
    pos_idx = (real_frequencies .> 0) .& (real_frequencies .< 300)
    p2 = plot(real_frequencies[pos_idx], real_magnitude_db[pos_idx] .- 30,
              title="FFT анализ собственных частот", 
              xlabel="Частота, Гц",
              ylabel="Амплитуда, dBm",
              xlim=(0, 200),
              linewidth=2,
              linecolor=:red)
    
    # Выделение найденных собственных частот
    experimental_freqs = natural_freqs
    experimental_amps = amplitudes .- 30
    
    scatter!(p2, experimental_freqs, experimental_amps, 
            markersize=8, 
            markercolor=:green,
            markerstroke=:black,
            label="Собственные частоты")
    
    # Подписи частот
    for (i, (freq, amp)) in enumerate(zip(experimental_freqs, experimental_amps))
        annotate!(p2, freq+5, amp+3, text(@sprintf("%.1f Гц", freq), 8, :black))
    end
    
    # Объединение графиков
    combined_plot = plot(p1, p2, layout=(2,1), size=(600,800))
    
    return combined_plot
end

# Создание графиков
analysis_plots = create_torsional_analysis_plots(time_data, speed_data, frequencies, magnitude_db)
Out[0]:
No description has been provided for this image

Явление резонанса на собственной частоте

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

Амплитуда крутящего момента 1 Н*м.

Для этого запустим модель, где в качестве источника воздействия используется не генератор импульса, а синусоидальный сигнал.

Схема модели:

torsional_oscillations_sin--1759313194565.png

С помощью переменной omega определяем частоту синусоидального сигнала перед каждым запуском симуляции.

In [ ]:
n = "torsional_oscillations_sin"
Out[0]:
"torsional_oscillations_sin"
In [ ]:
omega = 10
start_model_engee(n)
w = collect(simout["$n/Скорость.1"]);
torque = collect(simout["$n/Момент"]);
time_data1 = w[:,1];
speed_data1 = w[:,2];
time_torque = torque[:,1]
torque_data = torque[:,2]
p1 = plot(time_data1, speed_data1, xlabel="Время, с", ylabel="Скорость \nвращения, рад/с", color="red")
p2 = plot(time_torque, torque_data, xlabel="Время, с", ylabel="Крутящий \nмомент, Н*м")
plot(p1,p2, layout=(2,1))
Out[0]:
No description has been provided for this image
In [ ]:
omega = 20
start_model_engee(n)
w = collect(simout["$n/Скорость.1"]);
torque = collect(simout["$n/Момент"]);
time_data1 = w[:,1];
speed_data1 = w[:,2];
time_torque = torque[:,1]
torque_data = torque[:,2]
p1 = plot(time_data1, speed_data1, xlabel="Время, с", ylabel="Скорость \nвращения, рад/с", color="red")
p2 = plot(time_torque, torque_data, xlabel="Время, с", ylabel="Крутящий \nмомент, Н*м")
plot(p1,p2, layout=(2,1))
Out[0]:
No description has been provided for this image
In [ ]:
omega = 30
start_model_engee(n)
w = collect(simout["$n/Скорость.1"]);
torque = collect(simout["$n/Момент"]);
time_data1 = w[:,1];
speed_data1 = w[:,2];
time_torque = torque[:,1]
torque_data = torque[:,2]
p1 = plot(time_data1, speed_data1, xlabel="Время, с", ylabel="Скорость \nвращения, рад/с", color="red")
p2 = plot(time_torque, torque_data, xlabel="Время, с", ylabel="Крутящий \nмомент, Н*м")
plot(p1,p2, layout=(2,1))
Out[0]:
No description has been provided for this image
In [ ]:
omega = natural_freqs[1] #Первая собственная частота
start_model_engee(n)
w = collect(simout["$n/Скорость.1"]);
torque = collect(simout["$n/Момент"]);
time_data1 = w[:,1];
speed_data1 = w[:,2];
time_torque = torque[:,1]
torque_data = torque[:,2]
p1 = plot(time_data1, speed_data1, xlabel="Время, с", ylabel="Скорость \nвращения, рад/с", color="red")
p2 = plot(time_torque, torque_data, xlabel="Время, с", ylabel="Крутящий \nмомент, Н*м")
plot(p1,p2, layout=(2,1))
Out[0]:
No description has been provided for this image
In [ ]:
omega = 60
start_model_engee(n)
w = collect(simout["$n/Скорость.1"]);
torque = collect(simout["$n/Момент"]);
time_data1 = w[:,1];
speed_data1 = w[:,2];
time_torque = torque[:,1]
torque_data = torque[:,2]
p1 = plot(time_data1, speed_data1, xlabel="Время, с", ylabel="Скорость \nвращения, рад/с", color="red")
p2 = plot(time_torque, torque_data, xlabel="Время, с", ylabel="Крутящий \nмомент, Н*м")
plot(p1,p2, layout=(2,1))
Out[0]:
No description has been provided for this image

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

Выводы:

В данном примере была продемонстрирована комплексная модель анализа крутильных колебаний многомассовой системы.

Модель может быть адаптирована для различных крутильных систем: турбо-генераторных установок, приводов прокатных станов, судовых валопроводов и других промышленных механизмов.