modified: .gitignore
modified: README.md new file: requirement.txt new file: scripts_2/README.md new file: scripts_2/__init__.py new file: scripts_2/__pycache__/__init__.cpython-310.pyc new file: scripts_2/__pycache__/signal_utils.cpython-310.pyc new file: scripts_2/__pycache__/task3_identify.cpython-310.pyc new file: scripts_2/signal_utils.py new file: scripts_2/task3_identify.py new file: scripts_3/README.md new file: scripts_3/__init__.py new file: scripts_3/__pycache__/__init__.cpython-310.pyc new file: scripts_3/__pycache__/task4_predict_freq.cpython-310.pyc new file: scripts_3/task4_predict_freq.py
This commit is contained in:
33
scripts_3/README.md
Normal file
33
scripts_3/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 激振频率逆向预测 (Forced-Vibration Frequency Prediction)
|
||||
|
||||
## 项目简介
|
||||
本目录用于完成强迫振动频率逆向预测任务。脚本基于建筑顶部响应传感器 `WSMS00007` 的 Z 向数据,从稳态振动段中识别主频,反推出地震台输入的简谐波频率,并输出频率误差与可视化结果。
|
||||
|
||||
## 技术路线
|
||||
本目录的分析流程覆盖稳态段提取、主频精细识别与结果校验三个部分:
|
||||
|
||||
1. **稳态中段截取**
|
||||
对强迫振动时程执行滑动窗口搜索,从中间区域选出波动最稳定的响应片段,用于频谱分析。
|
||||
2. **顶部响应主频识别**
|
||||
读取 `WSMS00007` 的 `value3` 列,对稳态段进行加窗 FFT 与补零计算,并结合抛物线插值输出高精度激振频率。
|
||||
3. **文件名真值校验**
|
||||
从样本文件名中提取频率标签,计算预测值与真值之间的绝对误差。
|
||||
|
||||
## 核心模块说明
|
||||
|
||||
- **`task4_predict_freq.py`**
|
||||
任务主脚本。负责稳态段截取、频谱分析、抛物线插值、误差计算与频域图像生成。
|
||||
|
||||
## 使用指南
|
||||
|
||||
**1. 运行任务脚本**
|
||||
对单个强迫振动样本预测激振频率:
|
||||
```bash
|
||||
python scripts_3\task4_predict_freq.py --file downloads\Non_TMD\val\harmonic_5mm_0.75Hz.csv
|
||||
```
|
||||
|
||||
**2. 指定输出目录**
|
||||
将图像保存到自定义目录:
|
||||
```bash
|
||||
python scripts_3\task4_predict_freq.py --file downloads\Non_TMD\val\harmonic_5mm_0.75Hz.csv --output-dir evaluation_outputs\task4_custom
|
||||
```
|
||||
1
scripts_3/__init__.py
Normal file
1
scripts_3/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Signal-processing scripts for forced-vibration frequency prediction."""
|
||||
BIN
scripts_3/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
scripts_3/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
scripts_3/__pycache__/task4_predict_freq.cpython-310.pyc
Normal file
BIN
scripts_3/__pycache__/task4_predict_freq.cpython-310.pyc
Normal file
Binary file not shown.
180
scripts_3/task4_predict_freq.py
Normal file
180
scripts_3/task4_predict_freq.py
Normal file
@@ -0,0 +1,180 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
if __package__ is None or __package__ == "":
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from scripts_2.signal_utils import (
|
||||
TOP_RESPONSE_AXIS,
|
||||
TOP_RESPONSE_SENSOR,
|
||||
dominant_frequency_in_band,
|
||||
ensure_parent_dir,
|
||||
estimate_sampling_rate,
|
||||
get_middle_segment,
|
||||
parse_frequency_from_filename,
|
||||
read_sensor_signal,
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
project_root = Path(__file__).resolve().parents[1]
|
||||
parser = argparse.ArgumentParser(description="Predict the harmonic input frequency from top-response steady-state data only.")
|
||||
parser.add_argument(
|
||||
"--file",
|
||||
type=str,
|
||||
default=str(project_root / "downloads" / "Non_TMD" / "val" / "harmonic_5mm_0.75Hz.csv"),
|
||||
help="Path to a forced-vibration CSV such as data from Non_TMD val/test.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=str,
|
||||
default=str(project_root / "evaluation_outputs" / "task4_predict_freq"),
|
||||
help="Directory for plots and optional exported diagnostics.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show",
|
||||
action="store_true",
|
||||
help="Display the generated figure in addition to saving it.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def save_task4_figure(
|
||||
file_path: Path,
|
||||
output_dir: Path,
|
||||
time_segment: np.ndarray,
|
||||
signal_segment: np.ndarray,
|
||||
freqs: np.ndarray,
|
||||
magnitudes: np.ndarray,
|
||||
predicted_frequency_hz: float,
|
||||
true_frequency_hz: float | None,
|
||||
) -> Path:
|
||||
output_dir = ensure_parent_dir(output_dir)
|
||||
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
|
||||
fig.suptitle(f"Task 4 | Forced-Vibration Frequency Prediction | {file_path.name}")
|
||||
|
||||
axes[0].plot(time_segment, signal_segment, color="tab:blue", linewidth=1.0)
|
||||
axes[0].set_title(f"Steady middle segment from {TOP_RESPONSE_SENSOR} / {TOP_RESPONSE_AXIS}")
|
||||
axes[0].set_xlabel("Time (s)")
|
||||
axes[0].set_ylabel("Acceleration")
|
||||
axes[0].grid(True, alpha=0.25)
|
||||
|
||||
band_mask = (freqs >= 0.1) & (freqs <= 5.0)
|
||||
axes[1].plot(freqs[band_mask], magnitudes[band_mask], color="tab:purple", linewidth=1.1, label="Windowed FFT")
|
||||
axes[1].axvline(
|
||||
predicted_frequency_hz,
|
||||
color="tab:red",
|
||||
linestyle="--",
|
||||
linewidth=1.6,
|
||||
label=f"Predicted = {predicted_frequency_hz:.5f} Hz",
|
||||
)
|
||||
if true_frequency_hz is not None:
|
||||
axes[1].axvline(
|
||||
true_frequency_hz,
|
||||
color="tab:green",
|
||||
linestyle=":",
|
||||
linewidth=1.6,
|
||||
label=f"Filename truth = {true_frequency_hz:.5f} Hz",
|
||||
)
|
||||
axes[1].set_title("High-resolution spectrum from top steady-state response")
|
||||
axes[1].set_xlabel("Frequency (Hz)")
|
||||
axes[1].set_ylabel("Magnitude")
|
||||
axes[1].grid(True, alpha=0.25)
|
||||
axes[1].legend()
|
||||
|
||||
plt.tight_layout()
|
||||
figure_path = output_dir / f"{file_path.stem}_task4_predict_freq.png"
|
||||
plt.savefig(figure_path, dpi=180, bbox_inches="tight")
|
||||
return figure_path
|
||||
|
||||
|
||||
def analyze_forced_vibration(
|
||||
file_path: Path,
|
||||
output_dir: Path,
|
||||
show: bool = False,
|
||||
) -> dict[str, float | int | str]:
|
||||
file_path = Path(file_path).resolve()
|
||||
time_values, raw_signal = read_sensor_signal(file_path, sensor_code=TOP_RESPONSE_SENSOR, value_column=TOP_RESPONSE_AXIS)
|
||||
time_segment, signal_segment = get_middle_segment(time_values, raw_signal)
|
||||
sampling_rate = estimate_sampling_rate(time_segment)
|
||||
predicted_frequency_hz, freqs, magnitudes, peak_index = dominant_frequency_in_band(
|
||||
signal_segment,
|
||||
sampling_rate=sampling_rate,
|
||||
min_hz=0.1,
|
||||
max_hz=5.0,
|
||||
zero_padding_factor=32,
|
||||
)
|
||||
true_frequency_hz = parse_frequency_from_filename(file_path.name)
|
||||
absolute_error_hz = None if true_frequency_hz is None else abs(predicted_frequency_hz - true_frequency_hz)
|
||||
|
||||
figure_path = save_task4_figure(
|
||||
file_path=file_path,
|
||||
output_dir=output_dir,
|
||||
time_segment=time_segment,
|
||||
signal_segment=signal_segment,
|
||||
freqs=freqs,
|
||||
magnitudes=magnitudes,
|
||||
predicted_frequency_hz=predicted_frequency_hz,
|
||||
true_frequency_hz=true_frequency_hz,
|
||||
)
|
||||
if show:
|
||||
plt.show()
|
||||
plt.close("all")
|
||||
|
||||
return {
|
||||
"file_path": str(file_path),
|
||||
"sensor_code": TOP_RESPONSE_SENSOR,
|
||||
"axis": TOP_RESPONSE_AXIS,
|
||||
"sampling_rate_hz": float(sampling_rate),
|
||||
"segment_sample_count": int(signal_segment.size),
|
||||
"segment_start_time": float(time_segment[0]),
|
||||
"segment_end_time": float(time_segment[-1]),
|
||||
"predicted_frequency_hz": float(predicted_frequency_hz),
|
||||
"fft_peak_bin_hz": float(freqs[peak_index]),
|
||||
"fft_peak_magnitude": float(magnitudes[peak_index]),
|
||||
"true_frequency_hz": None if true_frequency_hz is None else float(true_frequency_hz),
|
||||
"absolute_error_hz": None if absolute_error_hz is None else float(absolute_error_hz),
|
||||
"figure_path": str(figure_path),
|
||||
"note": "Prediction uses only the top response sensor WSMS00007/value3. Base sensor WSMS00012/value1 is not used to infer the answer.",
|
||||
}
|
||||
|
||||
|
||||
def print_report(result: dict[str, float | int | str]) -> None:
|
||||
print("=== Task 4: Excitation Frequency Reverse Prediction ===")
|
||||
print(f"Input file: {result['file_path']}")
|
||||
print(f"Top response sensor: {result['sensor_code']} / {result['axis']}")
|
||||
print(f"Sampling rate: {result['sampling_rate_hz']:.4f} Hz")
|
||||
print(f"Steady segment samples: {result['segment_sample_count']}")
|
||||
print(f"Steady segment time range: {result['segment_start_time']:.4f} s to {result['segment_end_time']:.4f} s")
|
||||
print(f"Predicted excitation frequency: {result['predicted_frequency_hz']:.6f} Hz")
|
||||
if result["true_frequency_hz"] is not None:
|
||||
print(f"Filename frequency truth: {result['true_frequency_hz']:.6f} Hz")
|
||||
print(f"Absolute error: {result['absolute_error_hz']:.6f} Hz")
|
||||
else:
|
||||
print("Filename frequency truth: unavailable")
|
||||
print(f"Figure saved to: {result['figure_path']}")
|
||||
print(result["note"])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
result = analyze_forced_vibration(
|
||||
file_path=Path(args.file),
|
||||
output_dir=Path(args.output_dir),
|
||||
show=bool(args.show),
|
||||
)
|
||||
print_report(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user