Big talk о некоторых аспектах автоматизированной генерации документов. В ролях: LaTeX, Metapost, GNU Emacs, Make, m4, bash :-) Здесь не написано о том, как пользоваться Латехом, Метапостом, Емаксом, как писать мейкфалы, сценарии командной оболочки и не рассказано о хитростях макропроцессора m4. Текст длинный и справочно-унылый, читать долго.
Конкретно, описано следующее:
как вставлять (удобно) куски исходников в LaTeX-документ
как (автоматически) внедрять (с удобством) результаты численных расчётов в документ
как организовать простую визуализацию данных с включением графиков в документ
Хорошая документация неотделима от программы. Исходный текст на каком-то языке программирования и документация к нему должны быть представлениями одной сущности, разным способом выражая одно и то же — какой-то вычислительный процесс. Прозрачно связывать постановку задачи с чтивом для людей и внедрять в документ результат работы программы — тоже важно.
Популярным решением для связывания исходного кода и документации являются утилиты типа Doxygen (C, С++, Java, PHP и т. д.), gtk-doc (используется в проекте Gtk+), SchemeDoc (аналогичное Doxygen решение для Scheme), Texinfo и десятки других. Их сущность — в написании специально оформленных комментариев в исходном коде, которые впоследствии обрабатываются программой-документером, в результате чего получается хорошо оформленная справочная документация по программному интерфейсу (см. к примеру доки по PyGTK). Такая документация неизменно носит оттенок механоидности (хотя это не всегда минус), но очень удобна в генерации и использовании. Doxygen, кстати, ещё может при помощи Graphviz рисовать графы зависимостей между структурами кода.
Более высокий уровень — «Literate Programming» («грамотное программирование» («глупый» перевод «литературное» тоже неплохо отражает сущность)): ведущим принципом в написании исходного кода программы является его читабельность и понятность человеку, документация и компилируемый код генерируются из высокоуровневого описания — это путь, который используют различные отпрыски WEB (сегодня, в частности, на Web2C написан TeX, Metafont). Подход не только грандиозно увеличивает понимаемость и читабельность программы, но и переносит сам процесс программирования на более высокий уровень, заодно делая его ещё более осмысленным.
В этом семестре я выполнил небольшую курсовую работу по
дифференциальным уравнениям. В качестве языка реализации —
Scheme. Результатом программы является приближённое решение u(x)
уравнения d²u/dx² + n(x)u(x) = 0 (представленное в виде набора точек
графиков действительной и мнимой части u(x)) и ещё два коэффициента,
которые нужно по задаче найти. К реализации предлагается два метода
решения задачи (описать надо оба, заодно проверив друг другом
результат каждого из них).
Оформить всё я решил при помощи LaTeX.
Результат работы нужно (графики u(x) и два числа) представить в
отчете по курсовой.
Сорцы программы нужно включить в курсач (на бумаге) (тупость, но так надо; у меня не очень большой исходник получился)
Исходные данные (определение заданной по условию функции n(x) на
Лиспе и ещё некоторые числа) я просто записывал в отдельный файл
«постановки задачи». Он используется при вычислениях, и из него же
можно извлечь исходные данные и включить их в бумажный отчёт. Тогда
можно будет просто поменять постановку задачи (один файлик),
набрать make doc и получить отчёт по курсовой работе для новых
исходных данных без каких-либо дополнительных действий
руками. Итак, ещё одна нужность — выдернуть из постановки задачи
нужные чиселки и включить их в LaTeX-документ.
Для развёрнутого комментирования работы программы мне так же понадобилась возможность простого включения в LaTeX определения произвольной функции из произвольного файла в моём проектике.
Основным принципом является связывание бумажного отчёта и исходного кода программы (а так же её исходных данных и результатов) так, чтобы любое изменение программы немедленно и корректно отражалось в документации. В целом для обеспечения обновления определённых частей бумажного отчёта при изменении соответствующих аспектов программного кода использовалась сборочная система GNU make.
Файлы проекта полностью доступны в моём Mercurial-репозитории.
Доступен и скомпилированный мной PDF с курсачом.
Texdepend — небольшая утилита на Perl, являющаяся аналогом
makedepend для TeX-исходников. Как makedepend(1) распознаёт в
исходниках на Си директивы #include, так texdepend(1) рекурсивно
обрабатывает конструкции \input{}, \include{}, \usepackage{},
\includegraphics{} и другие. Из всех обрабатываемых texdepend
конструкций мне потребовались две — \input{} и \includegraphics{}.
Общий подход, который был применён для выполнения вышеобозначенных
требований — в исходнике на LaTeX пишется конструкция \input{} с
аргументом определённого вида, на исходник кастуется вызывается
texdepend, который выводит список «зависимостей». Полученные
зависимости формируются согласно своему типу, после чего движок TeX
просто включает сформированные файлы в документ.
Для управления процессом был написан Makefile + несколько простых и коротких скриптов на bash для генерации зависимостей.
В самом начале моего Makefile я написал:
include ${DOCNAME}-deps.mk
А в списке возможных целей описал правило сборки:
${DOCNAME}-deps.mk: ${DOCNAME}.tex
texdepend -o $@ -print=if $<
Команда texdepend -o $@ -print=if $< генерирует списки зависимостей
для файла ${DOCNAME.tex}, рассовывая их по переменным ${INCLUDES},
${FIGS} и т. п. Включая при помощи include сгенированный файл в
свой Makefile я получаю возможность указать эти переменные в
зависимостях PDF-ки с курсачом:
${DOCNAME}.aux: ${INCLUDES} ${FIGS} ${DOCNAME}.tex ${DOCNAME}.bib
pdflatex ${DOCNAME}
bibtex8 -B ${DOCNAME}
${DOCNAME}.pdf: report.aux
pdflatex ${DOCNAME}
pdflatex ${DOCNAME}
doc: ${DOCNAME}.pdf
Итак, когда Make получает задание обновить цель doc, по зависимостям
обновляется report.aux, а следовательно и списки зависимостей из
${INCLUDES}.
Остаток Makefile составляют, собственно, правила для сборки зависимостей. О них и пойдёт речь.
Кроме того, другим обобщением здесь является сходность всех правил сборки этих зависимостей: сценарии shell подготавливают данные для включения и при помощи макропроцессора m4 подставляют их в шаблоны. Полученное и внедряется в LaTeX-документ!
Конкретизирую задачу. Допустим, я хочу, чтобы, написав в нужном мне месте моего документа конструкцию
\input{matrices.scm-full-listing}
получить в этом самом месте красиво оформленный полный листинг файла
matrices.scm из моего проекта. Кроме того, я хочу уметь ссылаться на
этот листинг из других мест моего документа при помощи
\pageref{matrices.scm-full-listing}.
listings — замечательный пакет для LaTeX, дающих кучу возможностей
по оформлению включаемых в документ исходников. Умеет подсвечивать
синтаксис или выбранные лексемы в сорце, рисовать разные гламурные
рамки, добавлять индексные ссылки в листинг и многое другое. Для
включения исходника целиком я использовал определяемое этим пакетом
окружение lstlisting:
\begin{lstlisting}[label={__BASENAME-full-listing},title={Содержимое файла \filename{__BASENAME}}]
__SOURCE
\end{lstlisting}
Параметр label создаёт в месте этого листинг метку, на которую потом
можно будет ссылаться при помощи
\pageref{__BASENAME-full-listing}. Происхождение строчек
__BASENAME и __SOURCE пока неясно, хотя смысл их должен быть
очевиден — это имя включаемого файла и его содержимое. Механизм
подмены этих имён реальными значениями рассмотрен далее.
Конструкция \input{matrices.scm-full-listing} посредством texdepend
породит на уровне make необходимость обновления цели
matrices.scm-full-listing.tex; для таких целей в моём
Makefile прописано такое правило с действием по маске
(«Pattern rule») такого вида:
%-full-listing.tex: ${SOURCEDIR}/% \
source-full-listing.sh source-full-listing.tpl.tex
$(SHELL) source-full-listing.sh $< > $@
Единственная команда в списке правил обновления цели вызывает простой
шелл-скрипт source-full-listing.sh с аргументом, равным полному
имени включаемого файла. Сам скрипт выглядит так:
SOURCE=$(cat $1 | grep -v "[ ]*;; .*" | sed -e "s/^\([ ]*\);;@ /\1;; /")
BASENAME=$(basename $1)
m4 --define="__SOURCE"="${SOURCE}" \
--define="__BASENAME"="${BASENAME}" \
source-full-listing.tpl.tex
Его действие понятно: при помощи m4(1) он определяет два макроса
__SOURCE и __BASENAME, и выполняет замену их вхождений в файле
source-full-listing.tpl.tex на содержание исходника и его имя,
соответственно. Содержимое
source-full-listing.tpl.tex — как раз вышеприведённый фрагмент с
\begin{lstlisting} .. \end{lstlisting}.
Видно, что решения этой задачи почти нет — он состоит из пяти простых
строчек. На самом деле, самое главное — в этой строке из
source-full-listing.sh:
SOURCE=$(cat $1 | grep -v "[ ]*;; .*" | sed -e "s/^\([ ]*\);;@ /\1;; /")
По странному стечению обстоятельств, я пишу комментарии к
коду на английском, что, вероятно, может некоторым
образом смутить преподавателя. Простейшим из универсальных языков
является язык математики, поэтому я применил такое правило: обычные
комментарии пишутся с ;;, а особые «математические» — с ;;@.
Математический комментарий оформляется напрямую теховским окружением
$ .. $. При работе по включению сорца в TeX, обычные комментарии
тупо вырезаются, а математические — оставляются. При этом в преамбуле
документа указана команда \lstset{mathescape=true},
заставляющая пакет listings полностью обрабатывать (а не просто
«дословно» включать) выделенные при помощи $ .. $ части
кода. Например, при включении исходника со следующим текстом:
;; Check whether found a, b coefficients meet the conservation of
;; energy law:
;;@ $|A|^2 + |B|^2 = 1$
(define (energy-conserves? A B eps)
(< (abs (- 1
(+ (expt (magnitude A) 2)
(expt (magnitude B) 2))))
eps))
Комментарии с ;; просто удалятся, а формула |A|²+|B|²=1 корректно
заверстается в математическое окружение.
На самом деле, использование математических выражений для документирования определённого набора функций очень удобно — математическая нотация кратка и универсальна.
Для работы я решил развёрнуто прокомментировать основные вычислительные процессы, происходящие при построении решения. После краткого математического описания метода решения следует краткое описание процедур, относящихся к реализации метода, с отсылками к математическому описанию, перемежаемое собственно определениями этих процедур. То есть показывается связь кода программы и задачи. Хотелось иметь возможность написать по ходу текста что-то типа
\input{general-horner-eval__shared.scm-tag-listing}
И получить в этом месте содержимое функции general-horner-eval из
файла shared.scm (в дальнейшем описании для примера я буду
использовать именно эту функцию из этого файла)
Texdepend обработает эту конструкцию, потребовав от сборочной системы
обновления зависимости
general-horner-eval__shared.scm-tag-listing.tex. В правиле сборки
можно указать соответствующих файл с исходником (shared.scm) в
качестве зависимости — тогда листинг будет обновляться при изменениях
в коде. Оформлять листинги можно при помощи уже названного
listings.
Остаётся одно — как-то вытащить из определённого файла тело определённой функции. Дальнейшие манипуляции с ним и оформление — дел нехитрой техники.
Сначала я посмотрел на Scheme Elucidator — ещё одно решение для документирования кода на Scheme: в отличие от того же SchemeDoc, Elucidator не просто генерит доки по интерфейсу, а плотно связывает особенности вычислительного процесса с документацией. Изначально я собирался использовать это решение для курсача, однако отказался по нескольким причинам (пришлось бы перелопачивать вывод Elucidator (это HTML) в TeX; LAML, на котором Elucidator основан, не совсем удобен и негибок в установке; лишние значительные зависимости; исходная задача слишком простая для Elucidator).
Однако потом я вдруг вспомнил, что работаю в Emacs, где есть Semantic, так что написал коротенькую программку на Emacs Lisp с использованием Семантика, которая просто выводит на стандартный вывод содержимое заданного тега в заданном файле. Использование Semantic для этой задачи раскрывается в другой моей записи, сейчас будем считать, что команда
$ emacs --batch --load grok-lisp.el \
--exec '(print-tag-from-file "general-horner-eval" "shared.scm")' \
2> /dev/null
подаёт на стандартный вывод содержимое функции general-horner-eval в
файле shared.scm.
Я уже условился называть файлы, содержащие тела процедур для
включения, по схеме function__file-tag-listing.tex, так что
получается вот такое правило в Makefile:
%-tag-listing.tex: ${SOURCEDIR}/$$(call get-target-source,$$*) \
tag-listing.sh tag-listing.tpl.tex grok-lisp.el
$(SHELL) tag-listing.sh $(shell echo $* | sed -e "s/__.*//") $< > $@
Первая зависимость в этой хитроумной комбинации вычленяет из имени
цели general-horner-eval__shared.scm-tag-listing.tex часть shared.scm
(имя исходника, откуда берётся функция — для обновления листинга при
изменении сорца). Функция get-target-source задаётся в Makefile выше
по тексту таким образом:
define get-target-source
$(shell echo $1 | sed -e "s/.*__//")
endef
Остальные зависимости заставляют обновлять цель при изменении самих механизмов обновления, а собственно правило сборки вызывает команду
tag-listing.sh general-horner-eval shared.scm
В свою очередь,
tag-listing.sh
при помощи Emacs извлекает определение нужной функции из файла и
подставляет его (при помощи m4(1)) вместо строки __SOURCE в
файл-шаблон tag-listing.tpl.tex, общий для всех листингов и имеющий
такое содержание:
\begin{lstlisting}[frame=tbrl,frameround=tttt,aboveskip=0.5cm, belowskip=0.5cm]
__SOURCE
\end{lstlisting}
Он сходен с уже рассмотренным ранее шаблоном для включения сорца целиком и тоже использует пакет listings.
Ввиду указанного в Makefile перенаправления > $@ в конце вызова
tag-listing.sh, извлечённая из исходника и обрамлённая в конструкции
LaTeX функция попадает в
general-horner-eval__shared.scm-tag-listing.tex, который и
включается в документ.
В итоге, вся цепочка вызовов порождается лишь самим существованием команды
\input{general-horner-eval__shared.scm-tag-listing.tex}
в TeX-исходнике.
Нельзя сказать, что применение Elisp здесь обязательно — того же
эффекта получения кода произвольной функции можно добиться и при
помощи утилиты etags(1) + тех же grep(1), head(1) и sed(1);
однако выразительность такого решения была бы меньше. Кроме того, код
на Elisp очень быстро пишется и быстро отлаживается.
После решения задачи на Elisp я обнаружил Lisp2TeX — похожее
решение ровно для таких же целей — включать определённые части кода на
Scheme в TeX при помощи удобных конструкций. L2T очень гибок и
позволяет не просто включать сырец в TeX, но и делать pretty-print
листинга (красиво расставлять отступы), и включать результаты
вычисления произвольных S-выражений в него, и даже визуализировать
S-выражения при помощи pic — это просто замечательный уровень
работы. Кроме того, L2T умеет приводить включённые выражения Лиспа в
ещё более читабельный формат (превращать (f x y) в \varphi(x, y) и
гораздо более сложные выражения гибко преобразовывать по шаблонам).
Без сомнения, Lisp2TeX в связке с оформлением из уже упомянутого
LaTeX-пакета listings даёт гораздо более качественное решение,
нежели описанные выше костыли на основе Emacs+Semantic и обёрток на
shell. Lisp2TeX имеет возможность привязки к себе любых парсеров, что
делает его пригодным к использованию с сорцами на любых языках, а не
только всяких Лиспах.
Впрочем, я в итоге не стал сейчас юзать L2T. Имелись проблемы с компиляцией Lisp2TeX (плюс он подвязан на определённые реализации Scheme), и мне нравилось использовать texdepend для получения списка нужных для сборки курсача файлов данных, а так пришлось бы юзать и Lisp2Tex, и texdepend.
Буду писать большой серьёзный папир, обязательно применю Lisp2TeX, а сейчас он показался мне лишним — да и в решении через Elisp основные сложности лишь в написании правила для Makefile (остальные скрипты там по две строки максимум). Да, мне стыдно и у меня легкая форма NIH-синдрома.
Это совсем тупая мини-задача, но её решение я тоже опишу.
В проекте исходные данные хранятся в отдельных текстовых файлах и содержат определение одной-единственной функции, необходимой для расчёта, а также несколько определений числовых параметров. Определение функции на Scheme также предварено TeX-комментарием (о которых я писал выше), который также предполагается вставлять в документ. Выглядит такой файл примерно так:
;;@ $n(x) = \begin{cases} 35+3(x-1)^2 & 0<x<2\\ 36 & x \leq 0,\ x \geq 2 \end{cases}$
(define (f x)
(if (and (> x 0) (< x 2))
(+ 35 (* 3 (expt (- x 1) 2)))
36))
(define right-bound 2)
(define subintervals 100)
(define test-epsilon 0.0001)
Вставка нужного текста в документ происходит по команде
\input{statement.scm-initial-data}. Получается что-то вроде:
Созданию требуемого файла отвечает следующее правило Makefile:
%-initial-data.tex: ${SOURCEDIR}/% initial-data.sh initial-data.tpl.tex
$(SHELL) initial-data.sh $< > $@
Видно, что маленький скрипт initial-data.sh всего
лишь натравливается на файл с исходными данными. При помощи cat(1),
grep(1), sed(1) он втупую извлекает две цифирьки и размеченное
TeX-ом определение функции. Его работа завершается вызовом m4(1) на
очередной шаблон:
Рассматривается отрезок $[0; __RIGHTBOUND]$.
Функция показателя преломления среды $n(x)$ задана следующим
образом:
\begin{equation}\label{__LABEL}
__NTEX
\end{equation}
И вновь при помощи \label{__LABEL} генерируется метка вида
file.scm-initial-data, на которую я потому могу сослаться.
Тут снова пригодился бы Lisp2TeX, но непонятно, как он обработает код с явными Guile-измами (я использовал для реализации Guile) и помимо включаемых напрямую в TeX-документ двух несчастных коэффициентиков мне требовалось включить в него же созданный промежуточным преобразованием график, отрисованный Metapost.
Расчёты тривиальные и нересурсоёмкие, поэтому лучшее решение — самое
простое: выводить результаты (построенное решение и информацию о нём)
на стандартный вывод в формате, удобном для обработки обычными средствами
текстового преобразования (grep(1), tail(1), sed(1)), разрезать
этот вывод на удобные кусочки, которые впоследствии передаются в
каком-то виде на дальнейшую обработку:
приближённое решение в виде трёх столбцов вида x Re(u(x)) Im(u(x))
два искомых по условию задачи числа A и B
некоторые дополнительные данные, также обрабатываемые и включаемые в отчёт
Приближённое решение передаётся в Metapost для создания графика, который включается в документ, а найденные коэффициенты включаются в документ сразу.
Сначала идут три столбика (аргумент, действительная часть значения,
мнимая часть значения) из пары сотен строк. Они скучные, поэтом я
привожу лишь последние строки вывода расчётной программы. Блоки
разделяются классическим %%.
1.9452736318408 0.642259739680568 -0.761159271604867
1.95522388059701 0.687848180515163 -0.721539051510589
1.96517412935323 0.730907823000578 -0.679195358916038
1.97512437810945 0.771268488932788 -0.634284101266255
1.98507462686567 0.808769344860717 -0.586971358440383
1.99502487562189 0.843259561005711 -0.537432806000248
%%
A: -0.00478-0.00750i
B: 0.99996-0.00104i
conserves: yes
eps: 0.0001
method: fundmatrix
time: 2.987552
Текстовый обмен данными прозрачен и прост. Используйте простой текст!
Я пишу в своём документе:
\input{fundmatrix__statement.scm-results}
Это означает: вставить результаты расчёта методом fundmatrix с
исходными данными statement.scm. Это позволяет без лишних движений
вставлять в документ результаты расчётов с разными исходными данными и
разными методами.
В итоге вставляется текст такого вида (я скопировал этот отрывок из финальной PDF-ки):
В результате использования метода были получены следующие результаты: A = −0.00478 − 0.00750i (4.10) B = 0.99996 − 0.00104i Подтверждено, что значения A и B удовлетворяют условию 2.1: 2 2 |A| + |B| − 1 < 0.0001 Расчёт занял 2.987552 с.
В Makefile на эту тему представляют интерес два правила:
%-results: ${SOURCEDIR}/$$(call get-target-source,$$*) \
${SOURCEDIR}/$$(call get-target-method,$$*)-solution.scm \
${SHARED_SOURCES} results.sh
$(SHELL) results.sh $< $(call get-target-method,$*) dispatcher.scm ${SOURCEDIR} > $@
%-results.tex: %-results %-plot.mps texify-results.sh results.tpl.tex
$(SHELL) texify-results.sh $< > $@
Из этих двух первая цель описывает зависимость второй. Первая цель
довольно скучная, достаточно сказать лишь о том, что она запихивает в
файл с именем вида method_statement.scm-results результаты работы
расчётной программы (пример этих результатов приводился выше) с
методом method и исходными данными statement.scm. А вторая цель,
как видно, уже на этот файл натравливает пока неизвестный скрипт
texify-results.sh, который гламурно оформляет результаты расчёта.
Принцип его работы уже знаком: извлечь нужные данные, при помощи
m4(1) подставить их в шаблон results.tpl.tex,
который состоит из следующих букв:
define(`__CONSERVE_yes', `Подтверждено')
define(`__CONSERVE_no', `Не подтверждено')
В результате использования метода были получены следующие результаты:
\begin{equation}\label{__LABEL}
\begin{aligned}
&A=__A \\
&B=__B
\end{aligned}
\end{equation}
\emph{__CONSERVE_STATUS}, что значения $A$ и $B$ удовлетворяют условию
\ref{energy}:
\begin{displaymath}
\abs{\left( \abs{A}^2 + \abs{B}^2 \right) - 1} < __EPS
\end{displaymath}
Расчёт занял __TIME с.
Сам скрипт texify-results.sh определяет макросы __A, __B,
__TIME, __EPS, значения которых извлекаются из вывода расчётной
программы (строки на A:, B:, time:, eps: соответственно). Их
семантика очевидна. Макрос __LABEL заменяется на выражение вида
method__statement.scm-results, так что потом в документе я могу
сослаться на конкретно эти результаты при помощи \eqref или
\pageref.
Любопытен метод подстановки словосочетания «Подтверждено»/«Не
подтверждено» в зависимости от результатов расчёта (строчка
conserves: yes): __CONSERVE_STATUS раскрывается в один из двух
других макросов __CONSERVE_yes или __CONSERVE_no, которые уже
содержат желанные слова на русском языке. Это сделано для того, чтобы
вся информация об оформлении полностью содержалась в шаблонной части.
Metapost является замечательным средством для подготовки различной
графики для LaTeX и вообще основанных на TeX решениях (впрочем, может
использоваться и отдельно). Макропакет mpgraph для Metapost
позволяет без труда выводить несложные графики по заданному набору
точек.
Семантика включения графика решения немного отличается от уже отработанной схемы «texdepend — make — скрипты».
В исходнике отчёта о курсовой работе задана удобная команда \includeplot:
\newcommand{\includeplot}[2]{\begin{figure}[hb]
\centering
\includegraphics{#1__#2-plot.mps}
\caption{График $u(x)$ для функции преломления \eqref{#2-initial-data}}
\end{figure}}
Это сделано исключительно во избежание дублирования одинаковых
конструкций в исходнике. Кстати, в подпись в картинке внедряется
ссылка на ранее включённые в документ исходные данные, которым
соответствует график: \eqref{#2-initial-data}.
Включение графика решения, построенного при помощи метода, скажем
fundmatrix с исходными данными из statement.scm, осуществляется
так:
\includeplot{fundmatrix}{statement.scm}
Обычный texdepend(1) уже не может отследить такую
зависимость. Можно, однако, поступить так: график логично включать в
курсач после численных результатов расчёта. Как раз при включении этих
численных результатов можно генерировать и график (указав его в
качестве зависимости цели %-results.tex). Тогда конструкция с
\includeplot успешно найдёт картинку в файлике с именем вида
method__statement.scm-plot.mps. Итак, правила для его генерации
таковы:
%-results.tex: %-results %-plot.mps texify-results.sh results.tpl.tex
$(SHELL) texify-results.sh $< > $@
%-plot.mps: %-results plot-results.sh plot.tpl.mp
$(SHELL) plot-results.sh $<
Видно, что plot-results.sh — сценарий,
обрабатывающий всё тот же текстовый вывод работы расчётной
программы. Однако, в то время как texify-results.sh интересовался
разными данными после разделителя %%, plot-results.sh
концентрируется на куче чисел до него. Принцип же работы этого скрипта
уже знаком: он всего-навсего заставляет m4(1) подготовить результаты
расчётной программы к вычерчиванию (а именно, отрезать всё после %%
:-) и выполнить несколько макроподстановок в
Metapost-шаблоне, которые я перечислю далее.
Во-первых, нужно заставить Metapost давать выходному файлу
желаемое имя изменением переменной filenametemplate:
filenametemplate "__PLOT_PREFIX.mps";
Макрос __PLOT_PREFIX определяется в управляющем сценарии
plot-results.sh и заменяется на требуемую по условию строку вида
method_file.scm-plot, так что mpost(1) на выходе даст картинку с
нормальным именем, которую впоследствии увидит \includeplot в моём
LaTeX-исходнике.
На самом деле, шаблон plot.tpl.mp заточен под вывод графиков
произвольного количества функций в одной области. Мне это также
потребовалось для курсовой — в конце нужно сопоставить результаты
вычислений разными методами.
Далее, cобственно вычерчиванием графиков занимается следующий Metapost-код:
for file = __DATA:
color real_color, imag_color;
path real_plot, imag_plot;
real_color = blue*(0.5+uniformdeviate(5)/10)+green*uniformdeviate(2)/10+white*uniformdeviate(15)/100;
imag_color = red*(0.5+uniformdeviate(5)/10)+white*uniformdeviate(15)/100;
gdata(file, f,
augment.real_plot(f1, f2);
augment.imag_plot(f1, f3););
gdraw real_plot withpen __PEN withcolor real_color;
__LABEL(glabel.ulft(btex $\Re$ etex, 1) withcolor real_color*0.8;)
gdraw imag_plot withpen __PEN withcolor imag_color;
__LABEL(glabel.llft(btex $\Im$ etex, 1) withcolor imag_color*0.8;)
endfor;
В глаза здесь сразу бросаются другие три макроса:
__DATA, который заменяется на список файлов с описанием данных
для вычерчивания (или, в рассматриваемом случае, одно-единственное имя
файла с уже знакомыми столбцами x Re(u(x)) Im(u(x))).
__PEN определяется в зависимости от количества вычерчиваемых
наборов точек (когда строится один график, линия жирная, когда много —
тоньше). Применяется такой же подход, как в случае с макросом
__CONSERVE_STATUS в шаблоне для вывода численных результатов счёта:
__PEN раскрывается в один из двух других макросов __PEN_single или
__PEN_multi, которые определены уже в самом шаблоне для разделения
работы по подготовке данных и непосредственному оформлению графика:
define(__PEN_single',pencircle scaled 1.5')
define(__PEN_multi',pencircle scaled 0.8')
__LABEL, по аналогии с __PEN, определяется как нулевой
(раскрывающийся в пустоту) макрос, если я вывожу несколько графиков, и
как тождественный (раскрывающийся в свой аргумент без изменений),
если я вывожу график одного решения.
Конструкция
gdata(file, f,
augment.real_plot(f1, f2);
augment.imag_plot(f1, f3););
построчно читает из файла строки, записывая элементы первого и второго
столбца как (x, y) в первую кривую, а элементы первого и третьего — во
вторую кривую. Получаются две кривые, соответствующие действительной и
мнимой части вычерчиваемого решения. После этого две команды gdraw
выводят собранные кривые в область построения.
Я использовал LaTeX, Scheme, Metapost, однако описанные подходы применимы и к совершенно другим комбинациям средств:
я мог бы автоматизировать генерацию документа на любом другом языке, будь то XHTML или Texinfo (для них было бы нетрудно написать свои аналоги Texdepend)
я мог бы вставлять в свой документ гораздо более сложные результаты расчётов, полученные не простой программой на Scheme, а при помощи мощных средств численного анализа вроде Octave
я мог бы и использовать другое средство построения графиков: gnuplot, ePiX, pgf, PyXPlot. Главное, чтобы поддерживался простой текстовый командный интерфейс
Всё описанное очень просто в реализации благодаря использованию прозрачных и открытых форматов данных. LaTeX — это простой текст. Описания картинок MetaPost — простой текст. С простым текстом легко работать, его легко создавать простыми средствами и изменять при помощи, скажем, m4. Посчитал — разрезал вывод, отфильтровал нужное — подставил в шаблон — заинклудил в документ. А текстовый доступный Makefile позволяет быстро понять структуру процесса сборки документа.
Однако, говоря «простой», я не подразумеваю «лёгкий» :-)
Наверное, как-то так: UNIX — это просто, но не легко!
Комментарии:
отрадно, что в мгту еще не все забыли, что машина должна работать, а человек - думать
Замечательный пост! Сам по роду деятельности строю кучу графиков и включаю их в латеховские отчёты, статьи и труды конференций. Насколько же это проще того кошмара, который создают себе сами коллеги-виндовордовцы...
Относительно UNIX - это система для правильно ленивых. То есть: вместо того, чтобы биться лбом о стену, посидел, придумал скрипт. Потом использовал его ещё раз. :-)
зачОт! Моим студентам дам ссылку, пусть берут пример ;-)
Не уверен насчет Scheme, но Common Lisp выражение (and (x > 0) (x < 2)) эквивалентно (< 0 x 2)
Там тоже :-)
(< ..)говорит о возрастании списка аргументовЗамечательная и поучительная статья! Вообще, генерация генерируемого прямо в процессе компиляции документа — по идее правильный подход (есть правда потенциальные проблемы совместимости при попытке передать такой «исходник», скажем, в редакцию журнала; EPS-иллюстрации там примут нормально, а вот скрипт для генерации всего…)
Я, правда, пока до этого не дошёл :) Есть отдельный каталог с данными. Есть отдельная пачка скриптов чтобы по ним строить нужные графики. Отбираю их вручную. В документ вставляю уже готовыми. И далеко, далеко не все…
А какая это кафедра?
sv75, ФН-11