diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c9732042d --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +.vscode/launch.json + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +.vscode/ + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/lab_2/.gitignore b/lab_2/.gitignore new file mode 100644 index 000000000..82f927558 --- /dev/null +++ b/lab_2/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/lab_2/NIST_tests.py b/lab_2/NIST_tests.py new file mode 100644 index 000000000..43b7a88a5 --- /dev/null +++ b/lab_2/NIST_tests.py @@ -0,0 +1,145 @@ +import math +import numpy as np +from scipy import special + +def frequency_test(sequence: str, N: int) -> float: + """Функция реализует частотный побитовый тест NIST + + Args: + sequence (str): Бинарная последовательность длиной 128 + + Raises: + ValueError: Генерирует исключение если последовательность пустая + ValueError: Генерирует исключение если последовательность содержит какие-либо символы кроме "0" и "1" + ValueError: Генерирует исключение если длина последовательности не 128 бит + + Returns: + float: P-значение + """ + try: + if not sequence: + raise ValueError("Пустая последовательность") + if not all(bit in '01' for bit in sequence): + raise ValueError("Последовательность должна содержать только '0' и '1'") + + S_n = 0 + for bit in sequence: + S_n += 1 if bit == '1' else -1 + S_n = S_n/np.sqrt(len(sequence)) + + P = math.erfc(S_n / np.sqrt(2)) + + except ValueError as e: + print(f"Ошибка в данных: {e}") + return -1 + except Exception as e: + print(f"Неожиданная ошибка: {e}") + return -1 + + return P + +def indentical_bits(sequence: str, N: int) -> float: + """Функция реализует тест NIST на одинаковые подряд идущие биты + + Args: + sequence (str): Бинарная последовательность длиной 128 + + Raises: + ValueError: Генерирует исключение если последовательность пустая + ValueError: Генерирует исключение если последовательность содержит какие-либо символы кроме "0" и "1" + ValueError: Генерирует исключение если длина последовательности не 128 бит + + Returns: + float: P-значение + """ + try: + if not sequence: + raise ValueError("Пустая последовательность") + if not all(bit in '01' for bit in sequence): + raise ValueError("Последовательность должна содержать только '0' и '1'") + + zeta = sum(int(bit) for bit in sequence) / N + + if(np.abs(zeta-0.5) >= 2/np.sqrt(N)): + return 0 + + V_n = 0 + prev = sequence[0] + for bit in sequence[1:]: + if bit != prev: + V_n += 1 + prev = bit + + P = math.erfc(abs(V_n - (2 * N * zeta * (1-zeta))) / + (2 * np.sqrt(2 * N) * zeta * (1 - zeta))) + + except ValueError as e: + print(f"Ошибка в данных: {e}") + return -1 + except Exception as e: + print(f"Неожиданная ошибка: {e}") + return -1 + + return P + +def longest_seq(sequence: str, p_i: str, N: int, M: int) -> float: + """Функция реализует тест NIST на самую длинную последовательность единиц в блоке + Args: + sequence (str): Бинарная последовательность длиной 128 + + Raises: + ValueError: Генерирует исключение если последовательность пустая + ValueError: Генерирует исключение если последовательность содержит какие-либо символы кроме "0" и "1" + ValueError: Генерирует исключение если длина последовательности не 128 бит + + Returns: + float: P-значение + """ + try: + if not sequence: + raise ValueError("Пустая последовательность") + if not all(bit in '01' for bit in sequence): + raise ValueError("Последовательность должна содержать только '0' и '1'") + + num_of_blocks = N // M + blocks = (sequence[i * M:(i + 1) * M] for i in range(num_of_blocks)) + + statistics = [0] * 4 + + for block in blocks: + curr_n = 0 + max_n = 0 + + for bit in block: + if bit == "1": + curr_n += 1 + max_n = max(max_n, curr_n) + else: + curr_n = 0 + + match max_n: + case n if n <= 1: + statistics[0] += 1 + case 2: + statistics[1] += 1 + case 3: + statistics[2] += 1 + case _: + statistics[3] += 1 + + xi_square = 0 + p_in = (float(p) for p in p_i) + + for v, p in zip(statistics, p_in): + xi_square += (v - 16 * p) ** 2 / (16 * p) + + P = (special.gammaincc(3/2, xi_square/2)) + + except ValueError as e: + print(f"Ошибка в данных: {e}") + return -1 + except Exception as e: + print(f"Неожиданная ошибка: {e}") + return -1 + + return P \ No newline at end of file diff --git a/lab_2/Report_C++ b/lab_2/Report_C++ new file mode 100644 index 000000000..70da934ea --- /dev/null +++ b/lab_2/Report_C++ @@ -0,0 +1,10 @@ +Двоичная последовательность, сгенерированная с помощью стандартного ГСПЧ языка C++: +10011101001111101001111000001111111101011011100101011000100001110011011000000110011011111011000000111001111111000001001011110100 + +Результаты тестов NIST для данной двоичной последовательности: +Частотный побитовый тест: P = 0.37675911781158217 +Тест на одинаковые подряд идущие биты: P = 0.05915929910713417 +Тест на самую длинную последовательность единиц в блоке: P = 0.05296688162816608 + +Нулевая гипотеза принимается, альтернативная гипотеза опровергается: +Последовательность является истинно случайной diff --git a/lab_2/Report_Java b/lab_2/Report_Java new file mode 100644 index 000000000..f1ae836dc --- /dev/null +++ b/lab_2/Report_Java @@ -0,0 +1,10 @@ +Двоичная последовательность, сгенерированная с помощью стандартного ГСПЧ языка Java: +01111110011111001000110111101111011100010011000101011010001111100101011000010011100010000001111110110100100001000001001101010010 + +Результаты тестов NIST для данной двоичной последовательности: +Частотный побитовый тест: P = 1.0 +Тест на одинаковые подряд идущие биты: P = 0.4795001221869534 +Тест на самую длинную последовательность единиц в блоке: P = 0.31263961190997863 + +Нулевая гипотеза принимается, альтернативная гипотеза опровергается: +Последовательность является истинно случайной diff --git a/lab_2/binary_sequences/generate.cpp b/lab_2/binary_sequences/generate.cpp new file mode 100644 index 000000000..2439b1b46 --- /dev/null +++ b/lab_2/binary_sequences/generate.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +using namespace std; + +/** + * Generates random binary a sequence of length 128 + * + * @return string of generated binary sequence +*/ +string generate() { + std::random_device rd; + std::mt19937_64 gen(rd()); + + uint64_t part1 = gen(); + uint64_t part2 = gen(); + + std::bitset<64> bits1(part1); + std::bitset<64> bits2(part2); + + return(bits2.to_string() + bits1.to_string()); +} + +/** + * Main function that generates and prints a random binary sequence + * + * @return int Exit status code (0 for success) + */ +int main() { + std::cout << generate() << std::endl; + + return 0; +} \ No newline at end of file diff --git a/lab_2/binary_sequences/generate.java b/lab_2/binary_sequences/generate.java new file mode 100644 index 000000000..d60fd902c --- /dev/null +++ b/lab_2/binary_sequences/generate.java @@ -0,0 +1,19 @@ +import java.util.Random; + +public class Main { + /** + * Generates a 128-bit random binary sequence + * + * @param args Command line arguments (not used) + */ + public static void main(String[] args) { + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < 128; i++) { + sb.append(random.nextBoolean() ? '1' : '0'); + } + + System.out.println(sb.toString()); + } +} \ No newline at end of file diff --git a/lab_2/binary_sequences/generated_sequences.json b/lab_2/binary_sequences/generated_sequences.json new file mode 100644 index 000000000..fc8b46325 --- /dev/null +++ b/lab_2/binary_sequences/generated_sequences.json @@ -0,0 +1,4 @@ +{ + "cpp": "10011101001111101001111000001111111101011011100101011000100001110011011000000110011011111011000000111001111111000001001011110100", + "java": "01111110011111001000110111101111011100010011000101011010001111100101011000010011100010000001111110110100100001000001001101010010" +} \ No newline at end of file diff --git a/lab_2/main.py b/lab_2/main.py new file mode 100644 index 000000000..9c0e9d86c --- /dev/null +++ b/lab_2/main.py @@ -0,0 +1,78 @@ +import NIST_tests as tst +import tools as t + +def gen_report(binary_sequence: str, filename: str, lan: str, p_i: str, len_of_binary_sequense: int, len_of_block: int): + """Сохраняет в файле отчёт о проверке бинарной последовательности тестами NIST. + + Args: + binary_sequence (str): Проверяемая бинарная последовательность + filename (str): Имя файла, в котором необходимо сохранить отчёт + lan (str): Язык, с помощью которого была сгенерирована бинарная последовательность + + Raises: + ValueError: Значение P = -1 + """ + try: + with open(filename, 'w', encoding="utf-8") as f: + + results = [tst.frequency_test(binary_sequence, len_of_binary_sequense), + tst.indentical_bits(binary_sequence, len_of_binary_sequense), + tst.longest_seq(binary_sequence, p_i, len_of_binary_sequense, len_of_block)] + + print(f"Двоичная последовательность, сгенерированная с помощью стандартного ГСПЧ языка {lan}: ", file = f) + print(binary_sequence, file = f) + f.write("\n") + print("Результаты тестов NIST для данной двоичной последовательности: ", file = f) + + print(f"Частотный побитовый тест: P = {results[0]}", file = f) + print(f"Тест на одинаковые подряд идущие биты: P = {results[1]}", file = f) + print(f"Тест на самую длинную последовательность единиц в блоке: P = {results[2]}", file = f) + + check: list[bool] = [] + for P in results: + match P: + case p if p >= 0.01: + check.append(True) + case p if 0 <= p < 0.01: + check.append(False) + case _: + raise ValueError(f"Получено некорректное значение P: {P}") + + f.write("\n") + if all(check): + print("Нулевая гипотеза принимается, альтернативная гипотеза опровергается:", file = f) + print("Последовательность является истинно случайной", file = f) + else: + print("Альтернативная гипотеза принимается, нулевая гипотеза опровергается:", file = f) + print("Последовательность не является истинно случайной", file = f) + + except ValueError as e: + print(f"Ошибка в данных: {e}") + return + except Exception as e: + print(f"Неожиданная ошибка: {e}") + return + +def main(): + SETTINGS = t.read_json("settings.json") + SEQUENCES = t.read_json(SETTINGS["gen_seq"]) + + CPP_SEQUENCE = SEQUENCES["cpp"] + JAVA_SEQUENCE = SEQUENCES["java"] + + CPP_REPORT_PATH = SETTINGS["Lan_1_report"] + JAVA_REPORT_PATH = SETTINGS["Lan_2_report"] + CPP_LANGUAGE_NAME = SETTINGS["Lan_1"] + JAVA_LANGUAGE_NAME = SETTINGS["Lan_2"] + P_I = SETTINGS["p_i"] + SEQUENCE_LENGTH = int(SETTINGS["len_of_seq"]) + BLOCK_LENGTH = int(SETTINGS["len_of_block"]) + + gen_report(CPP_SEQUENCE, CPP_REPORT_PATH, CPP_LANGUAGE_NAME, + P_I, SEQUENCE_LENGTH, BLOCK_LENGTH) + gen_report(JAVA_SEQUENCE, JAVA_REPORT_PATH, JAVA_LANGUAGE_NAME, + P_I, SEQUENCE_LENGTH, BLOCK_LENGTH) + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/lab_2/requirements.txt b/lab_2/requirements.txt new file mode 100644 index 000000000..7f2a0d412 --- /dev/null +++ b/lab_2/requirements.txt @@ -0,0 +1,2 @@ +numpy==2.3.2 +scipy==1.16.1 diff --git a/lab_2/settings.json b/lab_2/settings.json new file mode 100644 index 000000000..28ed0d4c4 --- /dev/null +++ b/lab_2/settings.json @@ -0,0 +1,13 @@ +{ + "gen_seq": "binary_sequences/generated_sequences.json", + "len_of_seq": "128", + "len_of_block": "8", + + "Lan_1_report": "Report_C++", + "Lan_2_report": "Report_Java", + + "Lan_1": "C++", + "Lan_2": "Java", + + "p_i": ["0.2148", "0.3672", "0.2305", "0.1875"] +} \ No newline at end of file diff --git a/lab_2/tools.py b/lab_2/tools.py new file mode 100644 index 000000000..79feaf874 --- /dev/null +++ b/lab_2/tools.py @@ -0,0 +1,44 @@ +import json +from typing import Any, Dict + +def read_txt(file_name: str) -> str: + """Считывает текcт из .txt файла + Args: + file_name (str): Путь к файлу + Returns: + str: Содержимое файла + """ + try: + with open(file_name, 'r', encoding="utf-8") as f: + text: str = f.read() + return text + except Exception as e: + print(f"Error: {e}") + return "" + +def save_txt(file_name: str, text: str): + """Удаляет содержимое файла и сохраняет в нём новый текст. + Если файла не существует, создаёт его. + Args: + file_name (str): Путь к файлу + text (str): Текст, который необходимо сохранить + """ + try: + with open(file_name, 'w', encoding="utf-8") as f: + f.write(text) + except Exception as e: + print(f"Error: {e}") + +def read_json(file_name: str) -> Dict[str, Any]: + """Считывает данные из .json файла + Args: + file_name (str): Путь к файлу + Returns: + Dict[str, Any]: Словарь объектов, содеражавшихся в .json файле + """ + try: + with open(file_name, 'r', encoding='utf-8') as f: + return(json.load(f)) + except Exception as e: + print(f"Error: {e}") + return {} \ No newline at end of file