Skip to content

Commit 7301ebe

Browse files
committed
lab_1 in progress
1 parent 7367a0e commit 7301ebe

13 files changed

Lines changed: 430 additions & 14 deletions
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: AI Code Review
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
11+
jobs:
12+
ai-review:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: AI Code Review based on Opencode
16+
uses: Emeteil/ai_code_reviewer@v1.0.1
17+
with:
18+
language: Russian
19+
model: ""

‎.github/workflows/lint.yml‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
flake8:
9+
name: flake8 lint
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout repository
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Install flake8
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install flake8
24+
25+
- name: Run flake8
26+
run: flake8 .

‎justfile‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
venv := ".venv"
2+
python := if os_family() == "windows" { venv / "Scripts" / "python.exe" } else { venv / "bin" / "python" }
3+
4+
setup:
5+
@test -e "{{python}}" || python -m venv {{venv}}
6+
@"{{python}}" -m pip install -q -q -r requirements.txt flake8
7+
8+
lint: setup
9+
"{{python}}" -m flake8 .
10+
11+
run folder file="main.py": setup
12+
PYTHONPATH=. "{{python}}" "{{folder}}/{{file}}"

‎lab_1/main.py‎

Lines changed: 130 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,140 @@
1-
# 5 вариант
1+
from typing import TYPE_CHECKING
2+
if TYPE_CHECKING:
3+
from icecream import ic # noqa: F401
24

3-
import matplotlib.pyplot as plt
4-
from scipy.fftpack import fftfreq, fft
55
import numpy as np
6-
from numpy import cos, sin, pi
6+
import utils.debug # noqa: F401
7+
from matplotlib.axes import Axes # noqa: F401
78

8-
f = 3
9-
y = lambda t: cos(2*pi*f*t + pi/3) + sin(4+pi*f*t)
9+
from utils.complex_numbers import to_polar, to_rect # noqa: F401
10+
from utils.harmonic import (
11+
get_harmonic_oscillation,
12+
get_period, # noqa: F401
13+
get_phase_time_shift, # noqa: F401
14+
get_phase_at, # noqa: F401
15+
)
16+
from utils.fourier import discrete_fourier_transform # noqa: F401
17+
from utils.plotting import show_multi_plot, show_plot, show_subplots # noqa: F401
18+
from utils.sampling import get_linspace
1019

11-
x = np.linspace(0, 10, 1000)
1220

13-
y_x = y(x)
21+
def task_1_2() -> None:
22+
sampling_frequency = 1024
23+
start = 0
24+
stop = 1
25+
t = get_linspace(start=start, stop=stop, fs=sampling_frequency)
26+
27+
# <------------------------------------>
28+
# 1) Необходимо сгенерировать и визуализировать непрерывный сигнал
29+
frequency = 4
30+
31+
ic(
32+
frequency,
33+
sampling_frequency,
34+
)
35+
36+
y_1 = get_harmonic_oscillation(1, frequency, np.pi/3, wave=np.cos)(t)
37+
y_2 = get_harmonic_oscillation(1, 2*frequency, 0, wave=np.sin)(t)
38+
y = y_1 + y_2 # 5 вариант
1439

15-
fig, ax = plt.subplots()
16-
ax.plot(x, y_x)
17-
ax.set(xlabel='t', ylabel='y', title='y = cos(2*pi*f*t + pi/3) + sin(4+pi*f*t)')
40+
show_plot(
41+
t=t,
42+
func_val=y,
43+
title="cos(2*pi*f*t + pi/3) + sin(4*pi*f*t)",
44+
)
45+
# >------------------------------------<
46+
1847

19-
plt.show()
48+
# <------------------------------------>
49+
# 2) Определить максимальную частоту в спектре данного сигнала.
50+
max_freq = frequency * 2
51+
# >------------------------------------<
52+
53+
54+
# <------------------------------------>
55+
# 3) Определить минимальную необходимую частоту дискретизации
56+
# полученного сигнала (теорема Котельникова)
57+
min_freq_kotelnikov = max_freq * 2
58+
59+
ic(
60+
max_freq,
61+
min_freq_kotelnikov,
62+
)
63+
# >------------------------------------<
64+
65+
66+
# <------------------------------------>
67+
# 4) Оцифровать сигнал с полученной частотой дискретизации, выбрав
68+
# требуемое число отсчетов сигнала на длительности 1 секунда, сохранить
69+
# полученные значения в массив, пока не озадачиваясь разрядностью АЦП,
70+
# просто выбранные с частотой дискретизации значения с теми уровнями,
71+
# которые имеет функция в полученных точках.
72+
step = len(y) // int(min_freq_kotelnikov * (stop - start))
73+
y_sampled = y[::step]
74+
t_sampled = t[::step]
75+
76+
ic(
77+
y_sampled,
78+
t_sampled,
79+
)
80+
# >------------------------------------<
81+
82+
83+
# <------------------------------------>
84+
# 5) Выполнить прямое дискретное преобразование Фурье для массива
85+
# временных отсчетов сигнала и оценить ширину данного спектра. А также
86+
# объем памяти, требуемый для хранения данного массива (тип переменных в
87+
# массиве – на ваше усмотрение, float, int и пр.).
88+
y_dft = discrete_fourier_transform(y_sampled)
89+
N = len(y_dft)
90+
freqs = np.arange(N) * min_freq_kotelnikov / N
91+
amp = np.abs(y_dft) / N
92+
93+
ic(
94+
freqs,
95+
amp,
96+
)
2097

21-
max_freq = f*2
98+
threshold = 1e-6
99+
half = N // 2 + 1 # без зеркальной части
100+
significant = np.array([freqs[k] for k in range(half) if amp[k] > threshold])
101+
spectrum_width = significant.max()
102+
103+
ic(
104+
spectrum_width
105+
)
22106

23-
min_freq_kotelnikov = max_freq * 2
107+
def draw_signal(ax: Axes) -> None:
108+
ax.plot(t, y)
109+
ax.stem(t_sampled, y_sampled, linefmt="r-", markerfmt="ro", basefmt=" ")
24110

111+
def draw_full_spectrum(ax: Axes) -> None:
112+
ax.stem(freqs, amp)
113+
114+
def draw_half_spectrum(ax: Axes) -> None:
115+
ax.stem(freqs[:half], amp[:half])
116+
ax.axhline(threshold, color="gray", linestyle="--", label="порог")
117+
ax.axvline(spectrum_width, color="r", linestyle=":", label="ширина спектра")
118+
ax.legend()
119+
120+
show_subplots(
121+
draw_funcs=[draw_signal, draw_full_spectrum, draw_half_spectrum],
122+
titles=[
123+
"Сигнал и отсчеты",
124+
"Амплитудный спектр, все N гармоник (зеркальная часть)",
125+
"Амплитудный спектр без зеркальной части",
126+
],
127+
figsize=(9, 10),
128+
)
129+
# >------------------------------------<
130+
131+
132+
def main() -> None:
133+
task_1_2()
134+
135+
136+
if __name__ == "__main__":
137+
try:
138+
main()
139+
except KeyboardInterrupt:
140+
pass
570 KB
Binary file not shown.

‎requirements.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
icecream==2.2.0
2+
matplotlib>=3.10.9
3+
numpy>=2.1
4+
scipy>=1.14

‎utils/complex_numbers.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import numpy as np
2+
3+
4+
def to_polar(z: complex) -> tuple[float, float]:
5+
"""to_polar(z) -> r, theta"""
6+
# abs - теорема Пифагора, angle - арктангенс
7+
return np.abs(z), np.angle(z)
8+
9+
10+
def to_rect(r: float, theta: float) -> complex:
11+
"""to_rect(r, theta) -> z"""
12+
# просто формула Эйлера из тфкп
13+
return r * (np.cos(theta) + 1j * np.sin(theta))

‎utils/debug.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import builtins
2+
3+
try:
4+
import icecream
5+
from icecream import install
6+
7+
icecream.icecream.has_non_ascii_chars = lambda s: False
8+
9+
install()
10+
except ImportError:
11+
builtins.ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a)

‎utils/fourier.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from typing import Callable
2+
import numpy as np
3+
4+
5+
def discrete_fourier_transform(x: np.ndarray) -> np.ndarray:
6+
N = len(x)
7+
X = []
8+
for k in range(N):
9+
s = 0
10+
for n in range(N):
11+
s += x[n] * np.exp(-2j * np.pi * k * n / N)
12+
X.append(s)
13+
14+
return np.array(X)

‎utils/harmonic.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from typing import Callable
2+
import numpy as np
3+
4+
5+
def get_harmonic_oscillation(
6+
a: float,
7+
f: float,
8+
phi: float,
9+
wave: Callable[[np.ndarray], np.ndarray] = np.sin
10+
) -> Callable[[np.ndarray], np.ndarray]:
11+
"""y = a * wave(2 * pi * f * t + phi)"""
12+
def y(t: np.ndarray) -> np.ndarray:
13+
return a * wave(2 * np.pi * f * t + phi)
14+
15+
return y
16+
17+
18+
def get_period(f: float) -> float:
19+
"""T = 1/f"""
20+
return 1 / f
21+
22+
23+
def get_phase_time_shift(phi: float, f: float) -> float:
24+
"""-phi / (2 * pi * f)"""
25+
# угол делится на 2*pi, чтобы получить долю периода, затем умножается на период (делится на фазу)
26+
return -phi / (2 * np.pi * f)
27+
28+
29+
def get_phase_at(f: float, t: np.ndarray, phi: float = 0) -> np.ndarray:
30+
"""(2 * pi * f * t) + phi"""
31+
# время умножается на частоту и на 2*pi, чтобы получить фазу в радианах + начальная фаза
32+
return 2 * np.pi * f * t + phi

0 commit comments

Comments
 (0)