HEP Software School IV: задание A

/ / sw-hep-school :: , , , ,

На последнем семинаре мы познакомились со структурами в Си. Это лексическое средство открывает для нас, наконец, возможность придумать действительно практически-полезные домашние задания. Ввиду того что, согласно недавнему голосованию, слушатели понимают актуальность домашки, а так же учитывая требования предстоящей программы, настала пора сформулировать первое задание на самостоятельную работу.

Задача

Разработать структуру данных (struct Hist1D) представляющую одномерную гистограмму с равномерным биннингом $b_i, i = [1, ...N]$ и выделенными бинами для underflow/overflow. Структуру и сопутствующие функции целесообразно выделить в библиотеку, которую мы затем будем использовать как в простых рассчётных приложениях, так и для извлечения статистических характеристик Geant4.

Библиотека должна предоставлять следующие операции вокруг структуры, которые необходимо выразить в виде самостоятельных функций:

  • Инкремент бина на единицу; помимо самого объекта, функция принимает число v, подлежащее перенормировке: hst1d_fill(struct Hist1D *, float v)
  • Сумма гистограммы: $S = \sum\limits_1^N b_i$, float hst1d_sum(struct Hist1D *);
  • Среднее $\bar{v} = \sum\limits_1^N b_i / N$ hst1d_mean(struct Hist1D *);
  • Функция, принимающая указатель на массив $N$ вещественных чисел $w_i, i = [1, ... N]$, в которые она помещает посчитанные ею нормированные значения $w_i = b_i / S$, hst1d_weights(struct Hist1D *, float w[]);
  • Функция, реализующая расчёт среднего квадратического отклонения $\delta = \sqrt{ \sum\limits_1^N (b_i - \bar{v})^2/N }$, float hst1d_stddev(struct Hist1D *).

Разумеется, необходимо реализовать хотя бы один конструктор, инициализирующий поля данных структуры и динамически выделяющий блок данных "на куче" (on heap, посредством malloc()), и деструктор, осуществляющий очистку этого блока данных.

Целесообразно, так же, всюду использовать переопределённые типы данных для гибкой параметризации библиотеки (в шаблоне ниже BinCount_t -- тип данных бина, Value_t -- тип данных измеряемой величины).

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

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

Замечания

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

  • тип size_t -- знаковый целочисленный тип данных (как правило, long int), широко используемый стандартной библиотекой для хранения размера или дистанции указателей в памяти. Это typedef на обычный тип данных, объявленный где-то в заголовочных файлах стандартной библиотеки (при желании, вы даже можете где-то выловить grep'ом декларацию typedef на него).
  • квалификатор константности типа const в сигнатурах функций. В принципе, делает то же, что и при обычной декларации переменной. Целесообразно использовать его для гарантии неизменности данных внутри функции, которая, в силу своего предназначения не должна ничего изменять в данном объекте, а должна извлекать из него информацию: печатать в консоль, считать и возвращать сумму, среднее и т.д. Важно, что const никак не влияет на физические данные во время работы приложения, а есть только лексическое средство, применяемое программистом для контроля самого себя. Стоящий справа от астериска в типе указателя, он гарантирует константность данных по этому указателю, но не неизменность самого указателя. Т.е., для переменной const Hist1D * hstPtr, вы можете записать hstPtr = &otherHist, но запись hstPtr->vMin = 42 будет рассмотрена компилятором как ошибочная.
  • assert(). Понять, что делает эта функция достаточно просто: она прервёт процесс работающего приложения с ошибкой во времени выполнения программы, если выражение в скобках окажется ложным. Вы можете использовать эту функцию всюду, где хотите проконтролировать истинность некоторых алгоритмических предположений. Однако понять контекст использования её несколько сложнее: она не должна применяться для валидации входных данных всему приложению или алгоритму в целом -- для этого обычно используют выделенные механизмы сообщения об ошибке. То есть, это ещё один (наряду с const) механизм программисту контролировать правильность своей работы, и она не должна применяться для контроля корректности данных. По этой причине, эта конструкция имеет эффект только при активном определении макроса DEBUG (см. конец заметки про определения препроцессора): её применяют на этапе отладки кода, для контроля за простыми предположениями. В нашем случае, я всюду проверяю, верно ли, что указатель BinCount_t * bins структуры Hist1D отличен от нуля, иными словами, я пытаюсь защитить релевантные функции от вызова с объектом для которого уже был вызван деструктор.

Вы вольны переделать код который мы уже написали для простого представления гистограммы в предыдущих заметках. Кроме того, хорошим тоном будет написать небольшой тест для всех этих функций в точке входа, который вы можете изготовить из нашего самого первого примера с прямым методом Неймана.

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


Comments