Вот здесь точно нужен рефакторинг, есть идеи?

Бывают пет-проекты, а у нас получился проект с наработками, которые вроде бы могут быть полезны например студентам технических специальностей и просто всем кому интересно поразбираться с возможностями визуализации на C# + WPF, например, или с системой избыточного кодирования.

Мы со студентами сделали приложение для анализа характеристик LDPC кодов изначально на Java (Java код тоже присутствует в репозитории) потом я переписал его в виде проекта C# + WPF, чтобы добавить возможность конфигурации статистических экспериментов через визуальный интерфейс, а главное чтобы иметь возможность визуализации результатов экспериментов в виде графиков (обычных, в X, Y осях). Я как раз для работы сделал библиотеку для рисования обычных математических графиков по массивам значений с возможностью масштабирования области просмотра мышкой.

Думаю студентам любых технических направлений может пригодиться такая библиотека при том, что весь ее достаточно компактный исходный код локализован в проекте и доступен как для изучения так и для любых изменений и доработок.

Под катом ссылка на Гит-репозиторий с исходным кодом и обзор реализованной функциональности со скриншотами.

Проекты на C# и на Java доступны в репозитории на Гите. Java проект оказался совсем заброшенным и я не знаю в каком он состоянии, но картинки с графиками статистических характеристик LDPC кодов для диплома студенты генерировали именно с помощью этого Java проекта + некоторая библиотека визуализации для построения графиков на Питоне. Вообще код реализации статистического эксперимента на Java уже перенес один рефакторинг, я помог студентам определить правильную (в смысле работающую) конфигурацию классов на уровне сущностей для построения процесса декодирования, но это очень сложная тема. Мне самому, по прошествии года, наверно понадобится примерно неделя чтобы вспомнить-разобраться как там работает процес декодирования, потому что надо обратно загрузить в мозги теорию по алгоритмам Belief propagation, also known as sum–product message passing. В конце расскажу про организацию статистического эксперимента, это не особо сложно и надеюсь кому-то может быть полезным.

Итак если скомпилировать и запустить C# проэкт (видимо в Visual Studio, у меня была 2022-я), то можно увидеть очень не модный нынче на фоне возможностей командной строки, оконный интерфейс:

основная форма с вкладками

основная форма с вкладками

С параметрами конфигурации статистического эксперимента перечисленными в группе Configs. На группу Misc можно не обращать внимание эта группа была созданна для экспериментов с возможностями выбранного способа визуализации-хранения-редактирования параметров конфигурации. Вообще надо заметить что цель этого проекта (кода) была и остается проверка разных идей и способов реализации фактически произвольной функциональности, поэтому везде присутствуют артефакты таких экспериментов на разных стадиях отработки этих экспериментов. Поэтому если вы хотите, например, поэксперементировать или просто изучить возможности, например, визуализации-хранения-редактирования параметров конфигурации вы можете скачать этот проект.

Если я правильно закомитил проект на Гите сразу после запуска должна загрузиться валидная конфигурация эксперимента как на картинке и эксперимент можно запустить кнопкой BPStart. После чего надо подождать несколько секунд пока программа выполняет рассчеты для эксперимента. Это конечно очень серьезная недоработка для приложения с визуальным интерфейсом. Сделать прогресс-бар у меня так и не дошли руки, нет никакой индикации по прогрессу выполнения! Остается только ждать и надеяться что появится результат, но он вроде всегда появлялся, примерно вот в таком виде:

результаты численного эксперимента

результаты численного эксперимента

Который можно масштабировать просто выделяя мышью прямоугольник, который вы хотите подробнее рассмотреть:

область для масштабирования (увеличения)

область для масштабирования (увеличения)

Перерисованный график в пределах выбранной области:

23f02e59db5d2ebe56358d5f63535c0b.png

Зачем нужна самодельная библиотека рисования графиков

Однажды мне понадобилась визуализация массива данных в виде графика с осями и с подписями делений и желательно с сеткой и с возможностью масштабирования до отдельных сегментов графика. После такого перечисления задача не кажется очень простой не так ли, но я это все неоднакратно делал в своей работе, поэтому для меня это просто пара десятков часов (сложно оценить когда это делается урывками в течении года) рутины с гарантированным результатом.

Начать надо с того, что я конечно пытался скачивать и использовать существующие решения — библиотеки, пакеты, но все они оказались настолько навороченными что мне показалось, что мне проще и перспективнее (и интереснее) сделать свою собственную библиотеку, чем разбираться как же настроить отображение всех нужных мне перечисленных выше атрибутов обычного до банальности графика со всеми подписями и сеткой и масштабированием.

Основная проблема с существующими библиотеками рисования графиков в том, что они сделаны абсолютно универсальными, они позволяют отображать десятки видов графиков, гистограм, диаграм, … поэтому они требуют настройки сотен параметров с назначением и взаимосвязями которых, как мне показалось, не возможно разобраться в разумные сроки. Другим не маловажным аргументом разработки своего является, конечно, возможность собственного развития, ведь кроме того что мне эти чужие библиотеки показались слишком сложными, большую роль сыграло то, что мне было просто интересно порисовать самому и получить полный контроль над кодом который выполняет рисование. В итоге у меня получилась библиотека на 5–7 файлов исходного кода примерно по 100 строчек каждый. Там тоже присутствуют артефакты заброшенных экспериментов с кодом. Надо смотреть какой код действительно относится к реализации графиков, но кода очень не много в любом случае.  Мне кажется не плохой результат для библиотеки визуализации.

Как это работает с точки зрения C# кода

Собственно окно с графиком создается вот в такой функции:

        public static void makeGraph(double[] Yarr, double[] Xarr, double[] Y2arr)
        {
            Yarr = Array.ConvertAll(Yarr, y => Math.Log10(y));
            var gpb = CustomControls.graphXYControl.getDblXYGraph(Yarr, Xarr, "arr1");
            Y2arr = Array.ConvertAll(Y2arr, y => y != 0 ? Math.Log10(y):double.NegativeInfinity);
            gpb.AddYarray(Y2arr, "Y2arr");
            graphView graph = new graphView(gpb);
            //graph.setXlimits(1, 3);//какой то артефакт
            graph.Owner = wnd;
            graph.Show();
        }

Для масивов значений координат Y и X (Х-координаты тоже произвольные, а не линейно растущая последовательность!) создается объект типа GraphXYDblProc для отображения масива точек с произвольными координатами по оси Х с типом значений double. Этот объект типа GraphXYDblProc используется через интерфейс своего базового класса GraphProcBase. Дело в том что изначально для работы мне нужны были графики только для интовых (целочисленных) значений с постоянным шагом по оси-Х. Чтобы добавить поддержку графиков с произвольными значениями по оси Х и значениями координат с плавающей точкой мне пришлось выделить базовый класс в котором остались только функции рисования графиков и их атрибутов, а создание объектов визуализации в зависимости от типа исходных данных разделить на уровне дочерних классов, таким образом у меня появились:

  1. Класс GraphProc который инкапсулирует визуализацию массива целочисленных значений с заданным постоянным шагом по оси Х;

  2. Более сложный Класс GraphXYDblProc который инкапсулирует визуализацию массива точек заданных двумя массивами double значений по оси Х и по оси Y;

  3. Класс GraphProcBase который инкапсулирует функциональность связанную с атрибутами и поведением графика (сетка, подписи, масштабирование) не зависимо от типа данных по которым построен график.

Далее этот объект типа GraphProcBase (любой дочерний) передается объекту типа graphXYControl который является наследником стандартного прототипа визуальной компоненты UserControl и который обеспечивает стандартное взаимодействие с формой, с мышью.

Код который обеспечивает масштабирование вы найдете в файле: \ldpcExam\csharp\dipLdpc\CustomControls\graphXYControl.xaml.cs

в функциях:

private void drawingSurface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
private void drawingSurface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

также как и код генерирующий атрибуты оформления графика: оси (DrawAxis…), сетку, подписи, легенды (DrawLegends).

Другая вкладка в главном окне приложения

В проекте есть зачатки функциональности логирования. Если переключиться в главном окне на вкладку Output, вы увидите TextBox в который перенаправляются все логи.

другая вкладка в главном окне приложения

другая вкладка в главном окне приложения

Функции логирования сделаны статическими и публичными чтобы не размножать и не таскать ссылки на логер по всему проекту.

Также на этой вкладке присутствуют четыре кнопки Test, которые соответственно запускают соответствующие тесты.

  1. «Simple test» это тест графика в режиме отображения масива с целыми числами, просто показывает график с тестовыми (известными, легко визуально контролируемыми) данными.

  2. «Codeword test» генерирует-проверяет кодовое слово, логирует результат.

  3. «Gauss test» это тест генератора случайных чисел с нормальным распределением, а также еще один тест графика в режиме отображения масива с целыми числами, в графики специально вставлены артефакты для визуального контроля. Выглядит это примерно так:

    распределение Гауса

    распределение Гауса

    артефакт в ближайшем рассмотрении:

    артефакт на графике

    артефакт на графике

  4. «Wav test» вообще не имеет отношения к проекту, проверяет AAC кодированный аудио поток на валидность, если вам это о чем то говорит :), не обращайте внимание.

Про организацию статистического эксперимента

Схема в принципе стандартная:

  1. Генератор кодовых слов WordGen wg генерирует случайный код.

  2. Генератор шума генерирует шум, заменяет исходные биты кода случайными вероятностями с которыми они как бы приняты приемником, кажется где-то внутри этих вызовов:

...
chn.AddCodeWord(wg.getCodeWord());
double[] LLR = chn.convertToLLR();
...
  1. Декодер кода выполняет процедуру декодирования — пытается вернуть исходный код созданный генератором, кажется (хотя точно, написано же decode. :) где-то внутри этих вызовов:

                    bpAlg.StartWithLLR(LLR);
                    int[] resultCodeWord = bpAlg.decode(shiftBlocks);
  1. Глобальный алгоритм сравнивает результат полученный после декодера с исходным значением из генератора и собирает статистику в зависимости от уровня шума, длины кода, … я не помню все варианты.

В общем ничего сложного.

Заключение

Интернет и Хабр в часности наводнен рассказами о том как правильно строить архитектуру приложения, какие бывают правильные шаблоны проектирования, принципы ООП, KISS, DRY, SOLID, … и все это демонстрируется на высосанных из пальца примерах которые вызывают сомнения не то что в необходимости этих принципов, а в необходимости самого программирования.

Мне кажется у нас получился хороший пример программы-библиотеки, которой не помешает приличный рефакторинг. Есть идеи?

© Habrahabr.ru