218 lines
11 KiB
JavaScript
218 lines
11 KiB
JavaScript
export function getResponse(args) {
|
||
let force = args.spec_force; //спектр силы
|
||
let vibro = args.spec_vibro; //спектр вибрации
|
||
let type = args.type; //способ вычисления отклика
|
||
let view = args.view; //способ отображения спектра в линейных единицах или дБ
|
||
|
||
/* Алгоритм
|
||
1. Получить спектры ускорения и силы в комплексном виде (вибропреобразователь и молоток).
|
||
2. Преобразовать спектр силы (молотка) в комплексно-сопряжённый вид.
|
||
3. Получить кросс-спектр (перекрёстный) между ускорением и силой.
|
||
4. Вычислить деление кросс-спектра на спектр силы.
|
||
*/
|
||
|
||
/* Кросс-спектры
|
||
Сигнал силы в комплексно-сопряжённом виде
|
||
сигнал вибрации умножается на сигнал силы, а НЕ наоборот
|
||
умножение комплексных чисел по формуле:
|
||
(a1+b1i)*(a2+b2i)=(a1*a2-b1*b2)*(a1*b2+b1*a2)i
|
||
(a1*a2-b1*b2) - действительная часть
|
||
(a1*b2+b1*a2)i - мнимая часть
|
||
*/
|
||
|
||
let n = force.data.length; // длина массива спектра
|
||
let cross_real = new Float64Array(n); // типизированный массив для действительной части
|
||
let cross_imag = new Float64Array(n); // типизированный массив для мнимой части
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
const vReal = vibro.real[i]; // действительная часть спектра вибрации
|
||
const vImag = vibro.imag[i]; // мнимая часть спектра вибрации
|
||
const fReal = force.real[i]; // действительная часть спектра силы
|
||
const fImag = -force.imag[i]; // мнимая часть спектра силы сразу берём с обратным знаком
|
||
|
||
cross_real[i] = vReal * fReal - vImag * fImag;
|
||
cross_imag[i] = vReal * fImag + vImag * fReal;
|
||
}
|
||
|
||
// let cross_real2 = getCrossSpectrum(vibro.real, vibro.imag, force.real, force.imag).reXY;
|
||
// gtl.log.info('Амплитуда кросс', cross_real)
|
||
// gtl.log.info('Амплитуда кросс 2', cross_real2)
|
||
|
||
/* Вычисление частотного отклика
|
||
деление комплексных чисел по формуле:
|
||
(a+bi)/(c+di)=(a*c-b*d)*(a*d+b*c)i
|
||
(a*c-b*d) - действительная часть
|
||
(a*d+b*c)i - мнимая часть
|
||
*/
|
||
let resp_real = new Float64Array(n); // типизированный массив для действительной части
|
||
let resp_imag = new Float64Array(n); // типизированный массив для мнимой части
|
||
if (type == 0) {
|
||
// Вариант 1: Частотный отклик как деление кросс-спектра вибрации и силы на автоспектр силы
|
||
// ОШИБКА в моей формуле вычисления частотного отклика
|
||
// ИПРАВИЛ на:
|
||
// деление комплексных чисел по формуле:
|
||
// (a+bi)/(c+di)=(a*c + b*d)/(c^2 + d^2) + (c*b - a*d)/(c^2 + d^2)i
|
||
// (a*c + b*d)/(c^2 + d^2) - действительная часть
|
||
// (c*b - a*d)/(c^2 + d^2)i - мнимая часть
|
||
for (let i = 0; i < n; i++) {
|
||
const cReal = cross_real[i]; // действительная часть кросс спектра
|
||
const cImag = cross_imag[i]; // мнимая часть кросс спектра
|
||
const fReal = force.real[i]; // действительная часть спектра силы
|
||
const fImag = force.imag[i]; // мнимая часть спектра силы
|
||
|
||
const denominator = fReal * fReal + fImag * fImag;
|
||
resp_real[i] = (cReal * fReal + cImag * fImag) / denominator;
|
||
resp_imag[i] = (fReal * cImag - cReal * fImag) / denominator;
|
||
}
|
||
} else {
|
||
// Вариант 2: Частотный отклик как деление автоспектра вибрации на автоспектр силы
|
||
// Канал[j] / Канал[0]
|
||
for (let i = 0; i < n; i++) {
|
||
const vReal = vibro.real[i]; // действительная часть спектра вибрации
|
||
const vImag = vibro.imag[i]; // мнимая часть спектра вибрации
|
||
const fReal = force.real[i]; // действительная часть спектра силы
|
||
const fImag = force.imag[i]; // мнимая часть спектра силы
|
||
|
||
const denominator = fReal * fReal + fImag * fImag;
|
||
resp_real[i] = (vReal * fReal + vImag * fImag) / denominator;
|
||
resp_imag[i] = (fReal * vImag - vReal * fImag) / denominator;
|
||
}
|
||
}
|
||
|
||
// запись массива амплитуды и фазы
|
||
let resp_ampl = new Float64Array(n); // типизированный массив для амплитуды
|
||
let resp_phase = new Float64Array(n); // типизированный массив для фазы
|
||
const RAD_TO_DEG = 180 / Math.PI; //перевод радиан в градусы
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
const rReal = resp_real[i]; // действительная часть спектра отклика
|
||
const rImag = resp_imag[i]; // мнимая часто спектра отклика
|
||
|
||
resp_ampl[i] = Math.sqrt(rReal * rReal + rImag * rImag);
|
||
if (view > 0) { resp_ampl = resp_ampl.map((item) => (item = 20 * Math.log10(item / 1e-6))) }; //переводим в дБ
|
||
resp_phase[i] = Math.atan2(rImag, rReal) * RAD_TO_DEG;
|
||
}
|
||
|
||
// Приводим фазу к диапазону [0°, 360°]
|
||
for (let i = 0; i < n; i++) {
|
||
resp_phase[i] = resp_phase[i] % 360;
|
||
if (resp_phase[i] < 0) { resp_phase[i] += 360 }
|
||
}
|
||
|
||
let resp2 = getFrequencyResponse(vibro.real, vibro.imag, force.real, force.imag);
|
||
let resp_ampl2 = resp2.magnitude;
|
||
let resp_phase2 = resp2.phase;
|
||
|
||
// gtl.log.info('Амплитуда отклика', resp_ampl[0]);
|
||
// gtl.log.info('Амплитуда отклика 2', resp_ampl2[0]);
|
||
// gtl.log.info('Фаза отклика', resp_phase[0]);
|
||
// gtl.log.info('Фаза отклика 2', resp_phase2[0]);
|
||
|
||
return {
|
||
data: resp_ampl2,
|
||
phase: resp_phase2,
|
||
resolution: force.resolution
|
||
}
|
||
}; //рассчет перекрестного спектра
|
||
|
||
export function getAvgArray(arrays, remove) {
|
||
if (!arrays || arrays.length === 0) return []; //защита от пустого входного массива
|
||
|
||
const length = arrays[0].length; //длина вложенного массива
|
||
const result = new Array(length); //новый массив фиксированной длины, чтобы не пушить
|
||
const newArrays = arrays.filter((_, i) => !remove.includes(i)); //исключаем массивы по указанным индексам
|
||
const numArrays = newArrays.length; //количество вложенных массивов
|
||
|
||
if (newArrays.length === 0) return []; //защита если после фильтрации ничего не осталось
|
||
for (let i = 0; i < length; i++) {
|
||
let sum = 0;
|
||
for (let j = 0; j < numArrays; j++) { sum += newArrays[j][i] }
|
||
result[i] = sum / numArrays;
|
||
}
|
||
|
||
return result;
|
||
}; //вычисление среднего массива
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
function getCrossSpectrum(reX, imX, reY, imY) {
|
||
const n = reX.length;
|
||
const reXY = new Array(n); // действительная часть
|
||
const imXY = new Array(n); // мнимая часть
|
||
const power = new Array(n); // мощность кросс-спектра
|
||
let phase = new Array(n); // фаза (радианы)
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
// Комплексное сопряжение спектра imY* (мнимая часть спектра силы сразу берём с обратным знаком)
|
||
const a = reX[i], b = imX[i]; // X(f) = a + ib
|
||
const c = reY[i], d = -imY[i]; // Y(f) = c + id
|
||
|
||
// Почастотное умножение
|
||
reXY[i] = a * c - b * d; // действительная часть
|
||
imXY[i] = b * c + a * d; // мнимания часть
|
||
|
||
// Мощность кросс-спектра
|
||
power[i] = reXY[i] * reXY[i] + imXY[i] * imXY[i];
|
||
|
||
// Фаза = arctan(Im/Re)
|
||
phase[i] = Math.atan2(imXY[i], reXY[i]);
|
||
}
|
||
|
||
return { reXY, imXY, power, phase };
|
||
}
|
||
|
||
|
||
function getFrequencyResponse(reX, imX, reY, imY) {
|
||
const n = reX.length;
|
||
const magnitude = new Array(n); // амплитуда (линейная)
|
||
const magnitudeDB = new Array(n); // амплитуда (дБ)
|
||
let phase = new Array(n); // фаза (радианы)
|
||
const RAD_TO_DEG = 180 / Math.PI; //перевод радиан в градусы
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
const a = reX[i], b = imX[i]; // X(f) = a + ib
|
||
const c = reY[i], d = imY[i]; // Y(f) = c + id
|
||
|
||
// Знаменатель
|
||
const denominator = c * c + d * d; // знаменатель исходный вариант
|
||
// const denominator = a * a + b * b; // знаменатель вариант 2
|
||
|
||
// Избегаем деления на ноль
|
||
if (Math.abs(denominator) < 1e-10) {
|
||
magnitude[i] = 0;
|
||
magnitudeDB[i] = -Infinity;
|
||
phase[i] = 0;
|
||
continue;
|
||
}
|
||
|
||
// Почастотное умножение
|
||
const reH = (a * c + b * d) / denominator; // действительная часть
|
||
const imH = (c * b - a * d) / denominator; // мнимая часть исходный вариант
|
||
// const imH = (a * d - b * c) / denominator; // мнимая часть вариант 2 (поменял местами вычитаемые)
|
||
|
||
// Амплитуда
|
||
magnitude[i] = Math.sqrt(reH * reH + imH * imH);
|
||
|
||
// Амплитуда в дБ
|
||
magnitudeDB[i] = 20 * Math.log10(magnitude[i] / 1e-6);
|
||
|
||
// Приводим фазу сразу к диапазону [0°, 360°]
|
||
phase[i] = Math.atan2(imH, reH) * RAD_TO_DEG % 360;
|
||
if (phase[i] < 0) { phase[i] += 360 };
|
||
}
|
||
|
||
return { magnitude, magnitudeDB, phase };
|
||
}
|