Здравствуйте. В предыдущем уроке вы складывали синусоиды одной частоты через комплексные амплитуды и получали результат в непрерывном времени:
Теперь перейдём к тому, как такой сигнал представляется в программе. Компьютер не хранит непрерывную функцию : он хранит конечный массив значений, взятых в строго определённые моменты времени. В этой форме синусоиды будут использоваться далее для моделирования цепей, спектров, фильтров, модуляции и SDR-трактов.
К концу урока вы сможете задать частоту дискретизации, сгенерировать массив отсчётов синусоиды в NumPy и корректно показать его в Matplotlib как дискретный сигнал.
От непрерывного времени к отсчётам
Пусть исходный сигнал задан так:
где — амплитуда, — частота в герцах, а — начальная фаза в радианах.
При дискретизации мы измеряем этот сигнал не во все моменты времени, а через равные интервалы:
где:
- — период дискретизации, секунды;
- — частота дискретизации, отсчётов в секунду или Гц.
Моменты взятия отсчётов имеют номера :
Подставляя в исходную формулу, получаем дискретный синусоидальный сигнал:
Важно различать:
| Величина | Смысл | Единицы |
|---|---|---|
| частота самого сигнала | Гц | |
| частота, с которой берутся отсчёты | Гц | |
| расстояние по времени между отсчётами | с | |
| номер отсчёта | безразмерный | |
| число отсчётов в массиве | безразмерный |
Например, если синусоида имеет частоту , а частота дискретизации равна , то на один период приходится:
отсчётов. Это достаточно плотное представление для наглядного графика и простых моделей.
Строгое условие отсутствия алиасинга будет подробно разобрано в модуле о дискретизации. Пока достаточно придерживаться практического правила: выбирать заметно выше удвоенной максимальной частоты моделируемого сигнала.
Два массива: время и амплитуда
В NumPy сигнал обычно хранится в двух связанных массивах:
tсодержит моменты времени ;xсодержит амплитуды , измеренные или вычисленные в эти моменты.
Для сигналов с известной частотой дискретизации надёжнее сначала создать индексы отсчётов, а время получить делением на :
n = np.arange(N)
t = n / fs
Такое построение прямо повторяет формулу:
Функция np.arange(N) создаёт последовательность целых номеров:
[0, 1, 2, ..., N - 1]
Затем NumPy применяет арифметические операции сразу ко всему массиву. Это фундаментальный стиль работы с численными массивами: не нужно вручную вычислять синус в цикле для каждого отсчёта.
Посмотрите, как эти понятия связываются в коротком фрагменте видео Representing Signals in Python (Sampling) от канала Signal Processing with Paul.
Representing Signals in Python (Sampling)
Видео показывает практическую связь между частотой дискретизации, интервалом между отсчётами и визуализацией дискретного сигнала.
Начните с частоты дискретизации: обратите внимание на различие между f_s и T_s, а также на смысл критерия Найквиста. Затем посмотрите моменты отсчётов, где временная сетка строится через numpy.arange. Завершите фрагментом дискретного графика: он особенно важен для понимания, почему в stem нужно передавать реальные значения времени по оси x, а не только массив амплитуд.
Первая модель в NumPy
Создадим синусоиду с параметрами:
Возьмём длительность наблюдения . За это время синусоида совершит пять периодов, а массив будет содержать сто отсчётов.
import numpy as np
import matplotlib.pyplot as plt
# Параметры синусоиды
A = 1.5
f0 = 50.0
phi = np.pi / 6
# Параметры дискретизации
fs = 1000.0
duration = 0.10
N = int(fs * duration)
# Временная сетка и дискретный сигнал
n = np.arange(N)
t = n / fs
x = A * np.sin(2 * np.pi * f0 * t + phi)
print(f"Число отсчётов: {N}")
print(f"Период дискретизации: {1 / fs:.6f} с")
print(f"Первый отсчёт: {x[0]:.3f}")
Первое значение можно проверить вручную. При время равно нулю, поэтому:
Если программа вывела значение, близкое к 0.750, массив сформирован в соответствии с заданной фазой.
В выражении для x:
np.sin(2 * np.pi * f0 * t + phi)
f0 — скаляр, phi — скаляр, а t — массив из ста временных точек. NumPy выполняет умножение, сложение и вычисление sin поэлементно и возвращает массив той же длины.
Функции np.sin, np.cos и другие тригонометрические функции принимают аргумент в радианах. Поэтому множитель обязателен, когда частота задана в герцах:
— это фаза в радианах.
Как правильно показать отсчёты
Для непрерывного графика обычно используют plot, а для дискретного сигнала — stem.
fig, ax = plt.subplots(figsize=(9, 4))
ax.stem(t, x, linefmt="C0-", markerfmt="C0o", basefmt="k-")
ax.set_xlabel("Время, с")
ax.set_ylabel("Амплитуда")
ax.set_title("Дискретная синусоида: 50 Гц, fs = 1000 Гц")
ax.grid(True)
plt.show()
stem рисует отдельный маркер для каждого отсчёта и вертикальную линию до базовой линии. Такая визуализация не утверждает, что между соседними значениями существуют измеренные промежуточные точки.

Напротив, команда:
ax.plot(t, x)
соединяет соседние точки отрезками. Это часто удобно для обзора формы сигнала, но визуально создаёт впечатление непрерывной кривой. Сам массив x от этого не становится непрерывным.
Для изучения графических основ просмотрите пример Line plot из документации Matplotlib. Он показывает минимальную структуру программы: создание временного массива, вызов np.sin, настройку осей и вывод графика.
Line plot — Matplotlib 3.10.8 documentation
Прочитайте короткий пример из официальной документации Matplotlib. Он поможет соотнести уже написанный код с базовым шаблоном построения линейного графика.
На странице Line plot разберите единственный кодовый пример. Начните с создания данных и графика. Сопоставьте t с временной осью, s с массивом значений сигнала, а ax.plot(t, s) — с отображением пар координат. Обратите особое внимание на xlabel, ylabel, title и grid: в моделировании сигналов подписи с единицами измерения являются частью корректного результата.
Дискретный сигнал и «гладкая» опорная кривая
Иногда полезно показать одновременно:
- дискретные отсчёты, которые реально представлены в массиве;
- плотную опорную кривую той же математической синусоиды.
Для опорной кривой можно вычислить сигнал в гораздо большем числе точек. Это не восстановление измеренного аналогового сигнала, а лишь удобная визуализация известной формулы.
t_ref = np.linspace(0, duration, 2000, endpoint=False)
x_ref = A * np.sin(2 * np.pi * f0 * t_ref + phi)
fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(
t_ref,
x_ref,
color="C1",
linewidth=1.5,
label="Плотная опорная кривая"
)
ax.stem(
t,
x,
linefmt="C0-",
markerfmt="C0o",
basefmt="k-",
label="Отсчёты"
)
ax.set_xlabel("Время, с")
ax.set_ylabel("Амплитуда")
ax.set_title("Синусоида и её отсчёты")
ax.grid(True)
ax.legend()
plt.show()
Здесь np.linspace создаёт 2000 равномерно расположенных точек. Параметр endpoint=False исключает точку duration, поэтому временная сетка занимает интервал:
Это согласуется с построением дискретного массива из отсчётов:

В реальной системе между отсчётами нет сохранённых значений сигнала. Если они нужны, система должна либо вычислить их по известной модели, как в нашем примере, либо восстановить приближённое представление интерполяцией. Позже эта граница между дискретными данными, цифровой обработкой и восстановлением аналогового сигнала станет важной при изучении АЦП, ЦАП и SDR.
Выбор arange и linspace
Обе функции полезны, но отвечают на немного разные вопросы.
np.arange
Если известны количество отсчётов и частота дискретизации, наиболее прозрачна конструкция:
N = 100
n = np.arange(N)
t = n / fs
Она гарантирует ровно отсчётов, нумеруемых от до .
Можно встретить и такую форму:
Ts = 1 / fs
t = np.arange(0, duration, Ts)
Она хорошо передаёт физический смысл «брать отсчёт каждые секунд». Однако при дробных числах сравнение с конечной границей может зависеть от накопления ошибок представления чисел с плавающей точкой. Для воспроизводимой модели с фиксированным числом отсчётов удобнее вариант через индексы n.
np.linspace
linspace удобна, когда в первую очередь задан диапазон и нужное число точек:
t_ref = np.linspace(0, duration, 2000, endpoint=False)
Но следующий вариант — типичная ловушка:
t = np.linspace(0, duration, N)
По умолчанию linspace включает конечную точку. Тогда шаг составляет:
а не:
Если нужен именно шаг , либо используйте endpoint=False, либо стройте время через индексы:
t = np.arange(N) / fs
Частота в герцах и частота в радианах на отсчёт
В формуле непрерывного сигнала аргумент синуса выглядит так:
После подстановки его можно записать иначе:
где
— дискретная угловая частота, выраженная в радианах на отсчёт.
В нашем примере:
радиан на отсчёт.
Это одна и та же синусоида, записанная в двух системах координат:
- описывает физический сигнал во времени;
- рад/отсчёт описывает, насколько меняется фаза при переходе от одного элемента массива к следующему.
Для большей части прикладных расчётов удобнее задавать и в герцах, а NumPy уже вычислит нужную фазу.
Частые ошибки в первых моделях
Передача в sin частоты без множителя .
Неверно:
x = A * np.sin(f0 * t + phi)
Здесь f0 ошибочно интерпретируется как радианы в секунду, хотя была задана в герцах.
Верно:
x = A * np.sin(2 * np.pi * f0 * t + phi)
Построение графика только по x.
plt.stem(x)
Matplotlib в таком случае использует по оси индексы , а не секунды. Для отладки массива это допустимо, но не годится для физического графика сигнала. Передавайте временную ось явно:
plt.stem(t, x)
Смешение частоты сигнала и частоты дискретизации.
f0 определяет форму синусоиды, а fs определяет плотность её представления. Если изменить fs, но оставить неизменными f0 и duration, физическая частота сигнала не изменится; изменится число и расположение отсчётов.
Интерпретация соединяющей линии как сигнала между отсчётами.
plot проводит линию между точками для удобства просмотра. Дискретная последовательность состоит только из элементов массива:
Для подчёркивания этого факта используйте stem, особенно при невысокой частоте дискретизации.
Итоги
Дискретный синусоидальный сигнал строится по формуле:
Рабочий шаблон в Python выглядит так:
- Задать амплитуду , частоту , фазу , частоту дискретизации и число отсчётов .
- Создать индексы
n = np.arange(N). - Получить время
t = n / fs. - Вычислить массив
xчерезnp.sin. - Показать дискретный сигнал командой
ax.stem(t, x)и подписать оси с единицами измерения.
Вы уже умеете строить цифровое представление синусоиды. В следующем модуле этот навык станет основой для анализа спектра: мы начнём разбирать гармоники периодических сигналов и увидим, почему форма сигнала во времени определяет его составляющие в частотной области.
Can't find a good explanation? Sign up and we'll make it for you
Sign up