opencl_helpers_documentation-russian.tex 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. \documentclass[%showframe%
  2. ]{oclh_doc}
  3. \usepackage{fix-cm}% fix of Computer Modern font DO NOT MOVE!!!
  4. \usepackage{calc}% simple arithmetic in expressions
  5. \usepackage{etoolbox}% some Latex interfaces
  6. \usepackage{longtable,multirow}% tables
  7. \usepackage{adjustbox,%
  8. placeins, % processing of float objects
  9. caption} %
  10. %
  11. % page geometry
  12. %
  13. \usepackage[
  14. top = 0.06734007\paperheight,
  15. bottom = 0.06734007\paperheight,
  16. right = 0.1190476\paperwidth,
  17. left = 0.0952381\paperwidth
  18. ]{geometry}
  19. %
  20. % language and fonts
  21. %
  22. \usepackage{amsmath,amsfonts,amssymb,xfrac}
  23. \usepackage[cmintegrals,cmbraces]{newtxmath}
  24. \usepackage{xltxtra,polyglossia,csquotes}
  25. \usepackage{verbatim,fancyvrb,framed}
  26. \usepackage{relsize}
  27. \setmainlanguage{russian}
  28. \setotherlanguage{english}
  29. \setkeys{russian}{babelshorthands=true}
  30. \defaultfontfeatures{Scale=MatchLowercase,Mapping=tex-text}
  31. \input{fonts/font_settings-IBM_Plex.tex}
  32. % \input{fonts/font_settings-Computer_Modern.tex}
  33. % \input{fonts/font_settings-my_fonts.tex}
  34. %
  35. % paragraphs and align
  36. %
  37. \usepackage{indentfirst}
  38. \frenchspacing\sloppy\raggedbottom
  39. \setlength{\parindent}{0.0604762\paperwidth}%
  40. %%%%% additional colontitles settings
  41. \RequirePackage{fancyhdr}
  42. \fancyhf{}\renewcommand{\headrulewidth}{0pt}
  43. \fancyfoot{}
  44. \fancyfoot[R]{\thepage}
  45. \pagestyle{fancy}
  46. %
  47. % index
  48. %
  49. \usepackage[xindy]{imakeidx}
  50. \makeindex[options = -C utf8 -M texindy -M index_style_and_order ]
  51. %
  52. % references
  53. %
  54. \RequirePackage{hyperref}
  55. \RequirePackage{xcolor}
  56. \definecolor{DarkBlue}{rgb}{0,0,0.5}
  57. \hypersetup{colorlinks=true, linkcolor={black},
  58. urlcolor =DarkBlue, citecolor={black}}
  59. %
  60. % lists
  61. %
  62. \usepackage{enumitem}
  63. \newenvironment{CodePar}%
  64. {\Verbatim[samepage=true,frame=single]}%
  65. {\endVerbatim}%
  66. %
  67. \newenvironment{CodeParWithCC}[1]
  68. {\Verbatim[samepage=true,frame=single,commandchars=#1]}
  69. {\endVerbatim}
  70. %
  71. \newenvironment{ImpNote}
  72. {\setlength{\parindent}{0.0604762\paperwidth}%
  73. \setlength{\LTleft}{\parindent}%
  74. \setlength{\LTpre}{\parsep}\setlength{\LTpost}{\parsep}%
  75. \begin{longtable}{|p{\linewidth-\tabcolsep-1\parindent}}
  76. \noindent\textsf{Важное замечание:}}
  77. {\end{longtable}}
  78. %
  79. \newcommand{\SetLenVarWithWidth}[2]{%
  80. \ifdefined #1 \else%
  81. \newlength{#1}%
  82. \fi%
  83. \settowidth{#1}{#2}%
  84. }%
  85. \newcommand{\NmCnvDescript}%
  86. {\addtolength{\leftskip}{0.0604762\paperwidth}%
  87. \setlength{\parindent}{-0.0604762\paperwidth}}%
  88. %
  89. \newcommand{\verbI}[1]{\textit{\Verb|#1|}}%
  90. \newcommand{\verbIU}[1]{\underline{\smash{\textit{\Verb|#1|}}}}
  91. \title{Библиотека OpenCL\_helpers}
  92. \author{hk@r4in.tk\\ mns@r4in.tk}
  93. \makeindex
  94. \begin{document}
  95. \maketitle
  96. \section{Вводная информация}
  97. Библиотека OpenCL\_helpers создана для облегчения программирования
  98. многопоточных приложений, использующих GPGPU (General-purpose computing on
  99. graphics processing units -- вычисления общего назначения на графических
  100. процессорах). Библиотека не перекрывает всех потребностей программирования
  101. приложений, использующих GPGPU, она написана для упрощения создания приложений
  102. использующих многоуровневый (иерархический) параллелизм на одном компьютере. То
  103. есть, она позволяет распараллеливать задачу на потоки (threads) на основном
  104. вычислительном устройстве (CPU), а затем задачу каждого треда распараллеливать
  105. на устройстве GPGPU, разбивая GPU-потоки на команды выполняющие разные задачи.
  106. При создании библиотеки предполагалось, что каждый CPU-поток использует
  107. отдельное устройство GPGPU, однако не запрещено использовать одно устройство
  108. GPGPU в двух и более CPU-потоках. Параллелизм на уровне CPU обеспечивается
  109. средствами POSIX Threads, параллелизм на уровне GPGPU обеспечивается средствами
  110. библиотеки.
  111. Библиотека состоит из четырёх частей, не все из которых прямо связаны с
  112. параллелизмом и которые могут использоваться независимо друг от друга.
  113. Первая часть это средства сборки программ OpenCL~(п.\ref{sec:buildutils}).
  114. Средства сборки позволяют собирать программы OpenCL из командной строки и
  115. просматривать результат без написания и запуска собственного приложения.
  116. Вторая часть~(п.\ref{sec:libraryusing}) это сами CPU-функции OpenCL\_helpers и
  117. синтаксис, позволяющий создавать заголовочные файлы~(headers) общие для CPU и
  118. GPGPU программ.
  119. Третья часть~(п.\ref{sec:memalloc}) восполняет недостаток средств управления
  120. памятью в OpenCL C вводя средства выделения и освобождения памяти GPGPU,
  121. диагностики кучи~(heap) и реинтерпретации~(cast) указателей~(pointers).
  122. Четвёртая часть~(п.\ref{sec:squadmodel}) -- GPGPU-функции, позволяющие
  123. организовывать параллелизм внутри GPGPU, разбивая все множество GPGPU-потоков на
  124. команды, занимающиеся выполнением отдельных задач.
  125. \subsection{Сборка и установка}
  126. \label{sec:build_n_install}
  127. \subsubsection{Требования}
  128. \label{subsec:prerequisites}
  129. Предполагается, что вы имеете:
  130. \begin{enumerate}[leftmargin=2\parindent]
  131. \item Компьютер с установленной ОС Linux.
  132. \item Установленный и работающий компилятор языка C.
  133. \item Установленную и доступную стандартную библиотеку языка C (libc).
  134. \item Установленное программное обеспечение OpenCL версии 1.2 или младше любого
  135. производителя.
  136. \end{enumerate}
  137. \subsubsection{Получение исходного кода библиотеки OpenCL\_helpers}
  138. \label{subsec:getting_source}
  139. Получить копию исходного кода библиотеки OpenCL\_helpers можно по адресу
  140. \href{https://ggs.void.r4in.tk/hk/OpenCL_helpers/archive/master.tar.gz}%
  141. {\Verb|https://ggs.void.r4in.tk/hk/OpenCL\_helpers/archive/master.tar.gz|}
  142. после чего распаковать полученный архив.
  143. Кроме того, при наличии установленной СКВ Git~(\href{https://git-scm.com}%
  144. {\Verb|https://git-scm.com|}) можно получить исходный код
  145. библиотеки OpenCL\_helpers командой\par
  146. \indent\indent\verb|git clone |%
  147. \href{https://ggs.void.r4in.tk/hk/OpenCL_helpers.git}%
  148. {\Verb|https://ggs.void.r4in.tk/hk/OpenCL\_helpers.git|}
  149. \subsubsection{Сборка}
  150. Для сборки по умолчанию необходимо перейти в каталог \verb|OpenCL_helpers| и
  151. выполнить команду
  152. \indent\indent\verb|make|
  153. \noindent%
  154. Допустимо использовать ключ \verb|-j| для многопоточной сборки. Если команда
  155. завершена без ошибок, то в каталоге \verb|OpenCL_helpers/build| появятся файлы:
  156. \par
  157. \indent\indent\verb|liboclh.so.|\verbI{I}\verb|.|\verbI{J}%
  158. \verb| oclh_br oclh_cr oclh_lr|\par
  159. \noindent%
  160. и несколько подкаталогов \verb|*.o| с объектами. В имени первого файла
  161. \verbI{I} -- майорная версия библиотеки, а \verbI{J} -- минорная. Каждый файл
  162. может быть собран отдельно командами:\par
  163. \indent\indent\verb|make oclh_library|\par
  164. \indent\indent\verb|make oclh_builder|\par
  165. \indent\indent\verb|make oclh_compiler|\par
  166. \indent\indent\verb|make oclh_linker|\par
  167. При необходимости возможна сборка для отладки (debug) командой\par
  168. \indent\indent\verb|make debug|
  169. \subsubsection{Установка}
  170. Установка осуществляется командой:\par
  171. \indent\indent\verb|make install|\par
  172. \noindent%
  173. В результате будет создан каталог \verb|~/opt/oclh|, куда в подкаталоги
  174. \verb|bin|, \verb|lib|, \verb|include| будут скопированы исполняемые файлы, файл
  175. библиотеки и заголовочные файлы соответственно. Затем целесообразно добавить
  176. каталог \verb|~/opt/oclh/bin| в переменную окружения \verb|PATH|, а каталог
  177. \verb|~/opt/oclh/lib| -- в переменную окружения \verb|LD_LIBRARY_PATH|.
  178. Можно изменить целевой путь если задать команду
  179. \indent\indent\verb|make PRFX_PATH=|\verbI{путь\_установки}\verb| install|
  180. \subsubsection{Удаление}
  181. Удаление производится командой
  182. \indent\indent\verb|make uninstall|
  183. \noindent%
  184. либо
  185. \indent\indent\verb|make PRFX_PATH=|\verbI{путь\_установки}\verb| uninstall|
  186. \noindent%
  187. если установка производилась не в каталог по умолчанию.
  188. \subsubsection{Документация}
  189. Документация библиотеки OpenCL\_helpers собирается отдельно. Для её сборки
  190. необходима система вёрстки \XeTeX /\XeLaTeX\ или иная \TeX /\LaTeX-система.
  191. Система \XeTeX\ и сопутствующие пакеты поставляются в составе дистрибутива \TeX
  192. ~Live~(\href{https://www.tug.org/texlive/}%
  193. {\Verb|https://www.tug.org/texlive/|}). Использование системы отличной от
  194. \XeTeX\ может потребовать внесения изменений в исходный код документации.
  195. Кроме непосредственно системы вёрстки понадобится ещё ряд пакетов, например,
  196. xindy для составления предметного указателя. Все использованные при составлении
  197. документации пакеты свободно доступны в составе дистрибутива \TeX ~Live.
  198. Сама сборка документации осуществляется из каталога
  199. \verb|OpenCL_helpers/documentation| запуском сценария сборки\par
  200. \indent\indent\verb|./build_script|\par
  201. \noindent%
  202. Если в ходе исполнения данного сценария не возникло ошибок, то в каталоге
  203. \verb|OpenCL_helpers/documentation/build| появятся файлы\par
  204. \indent\indent\verb|opencl_helpers_documentation-russian.pdf|\par
  205. \indent\indent\verb|opencl_helpers_documentation-english.pdf|\par
  206. \noindent%
  207. с документацией на русском и английском языках соответственно.
  208. При сборке будут использованы шрифты семейства IBM~Plex, но можно вернуться к
  209. базовому семейству Computer~Modern просто раскомментировав строку\par
  210. \indent\indent\verb|\input{fonts/font_settings-Computer_Modern.tex}|\par
  211. \noindent в преамбуле исходного кода документации.
  212. \subsection{Формат файла-журнала}
  213. \label{subsec:logformat}
  214. \index{формат файла-журнала}%
  215. Средствами библиотеки можно вести файлы-журналы~(logs) о происходящих в
  216. приложении событиях, кроме того сама библиотека при необходимости будет
  217. осуществлять записи в файл(ы)-журналы. Описание функций журналирования
  218. дано~в~п.\ref{subsec:logfunctions}.
  219. Стандартная запись в файле журнале выглядит как
  220. \begin{CodeParWithCC}{\\\{\}}
  221. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} \textit{содержание_сообщения}
  222. \end{CodeParWithCC}
  223. \noindent где\par
  224. {%
  225. \setlength{\leftskip}{0pt}%
  226. \setlength{\LTpre}{\smallskipamount}\setlength{\LTpost}{\smallskipamount}%
  227. \setlength{\LTleft}{2\parindent-\tabcolsep}
  228. \SetLenVarWithWidth{\Acol}{\verbI{YYYY}}%
  229. \SetLenVarWithWidth{\Bcol}{--}%
  230. \begin{longtable}%
  231. {p{\Acol}p{\Bcol}p{\linewidth-\LTleft-\Acol-\Bcol-5\tabcolsep}}
  232. \verbI{YYYY}&--&год, записанный четырьмя десятичными цифрами;\\
  233. \verbI{MM}&--&месяц года, записанный двумя десятичными цифрами от 01 до 12;\\
  234. \verbI{DD}&--&день месяца, записанный двумя десятичными цифрами от 01 до 31;\\
  235. \verbI{hh}&--&час дня, записанный двумя десятичными цифрами от 00 до 23;\\
  236. \verbI{mm}&--&минута часа, записанная двумя десятичными цифрами от 00 до 59;\\
  237. \verbI{ss}&--&секунда минуты, записанная двумя десятичными цифрами от 00 до 59;
  238. \\
  239. \verbI{HHHH}&--&последние два байта адреса рабочей конфигурации устройства
  240. GPGPU~(workset,~подробнее~см.~п.\ref{subsec:structures}), записанные четырьмя
  241. шестнадцатеричными цифрами.
  242. \end{longtable}
  243. }\par
  244. \noindent%
  245. \verbI{содержание\_сообщения}~--~может быть любым текстом переданным функции
  246. журналирования, однако в коде самой библиотеки соблюдаются по возможности
  247. следующие соглашения:
  248. \begin{enumerate}
  249. \item Информация об объектах (сущностях) OpenCL записывается как
  250. \verbI{тип\_объекта}\verb|_0x|\verbI{HHHH}, где \verbI{HHHH}~--~последние два
  251. байта адреса объекта, записанные четырьмя шестнадцатиричными цифрами. Так,
  252. например, устройство GPGPU может быть записано как \verb|dev_0x2a78|, а
  253. платформа как \verb|platform_0xf190|. Исчерпывающий список сущностей OpenCL
  254. дан в спецификациях OpenCL.
  255. \item В качестве разделителей блоков информации в сообщении и маркирования
  256. относительности блоков используется символ <<\verb+|+>>. Так
  257. запись\nopagebreak
  258. \begin{CodePar}
  259. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | dev_0xf260 | ...
  260. \end{CodePar}
  261. обозначает, что сообщение относится к OpenCL-контексту \verb|0x9f60|,
  262. использующему устройство GPGPU \verb|0xf260|.
  263. \item В случае вывода в журнал информации, являющейся уточнением, перед ней
  264. ставится дополнительный пробел, например:\nopagebreak
  265. {\scriptsize
  266. \begin{CodePar}
  267. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | Reference count: 1
  268. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | Number of devices: 1
  269. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | Device ID(s): 0x1acf260
  270. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | dev_0xf260 | GPU: 15 units/17...
  271. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | dev_0xf260 | Memory: 8116.43...
  272. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | dev_0xf260 | Vendor: NVIDIA Corp...
  273. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | dev_0xf260 | Model: GeForce GT...
  274. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | Context properties:
  275. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | Platform: 0xf190
  276. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | platform_0xf190 | Profile: FULL_PROFILE
  277. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | platform_0xf190 | Version: OpenCL 1...
  278. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | platform_0xf190 | Name: NVIDIA CUDA
  279. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | platform_0xf190 | Vendor: NVIDIA Corp...
  280. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | platform_0xf190 | Extensions: cl_khr...
  281. 2019-06-03 15:42:47 ws_0x9c00 context_0x9f60 | Is user responsible for sync: Undefined (presumable No)
  282. \end{CodePar}
  283. }
  284. \item В случае возникновения ошибки при выполнении функции библиотеки в
  285. журнал будет добавлена запись, начинающаяся с \verb|oclerr:| и содержащая
  286. информацию о всех вызовах функций начиная с библиотечного и до OpenCL API. Так
  287. сообщение в журнале:\nopagebreak
  288. \begin{CodeParWithCC}{\\\{\}}
  289. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} oclerr:
  290. _ghf_getBuildStatus/clGetProgramBuildInfo/CL_PROGRAM_BUILD_STATUS
  291. returned error -3 - CL_COMPILER_NOT_AVAILABLE
  292. \end{CodeParWithCC}
  293. значит, что функция \verb|_ghf_getBuildStatus| вызвала функцию OpenCL API
  294. \verb|clGetProgramBuildInfo| с аргументом \verb|CL_PROGRAM_BUILD_STATUS| и
  295. получила в ответ ошибку с кодом \verb|-3|, которая обозначает
  296. \verb|CL_COMPILER_NOT_AVAILABLE|.
  297. \end{enumerate}
  298. Учитывая, что для одного запуска приложения адреса сущностей OpenCL уникальны,
  299. то с высокой вероятностью сочетание названия объекта и двух последних байт
  300. адреса тоже уникально. Поэтому использование этих соглашений позволяет с помощью
  301. фильтрации подстроки получить из файла-журнала необходимую информацию по
  302. отдельному объекту OpenCL.
  303. Кроме стандартной записи файла журнала существует ещё запись-заголовок, которая
  304. выглядит как\par
  305. {\small%
  306. \begin{CodeParWithCC}{\\\{\}}
  307. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} _______________
  308. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} \textit{Текст_заголовка}
  309. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} ~~~~~~~~~~~~~~~
  310. \end{CodeParWithCC}
  311. }\par
  312. \noindent и запись-разделитель\par
  313. {\small%
  314. \begin{CodeParWithCC}{\\\{\}}
  315. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} ____________________________________________________
  316. \textit{YYYY}-\textit{MM}-\textit{DD} \textit{hh}:\textit{mm}:\textit{ss} ws_0x\textit{HHHH} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  317. \end{CodeParWithCC}
  318. }
  319. \input{name_conventions-russian.tex}
  320. \section{Средства сборки программ OpenCL}
  321. \label{sec:buildutils}
  322. В состав библиотеки входят три исполняемых файла:\nopagebreak\par
  323. \begin{itemize}[leftmargin=1.75\parindent]
  324. \item\verb|oclh_cr| -- осуществляет компиляцию программы OpenCL в объект
  325. OpenCL;
  326. \item\verb|oclh_lr| -- осуществляет компоновку объектов OpenCL;
  327. \item\verb|oclh_br| -- осуществляет полную сборку программы OpenCL.
  328. \end{itemize}\nopagebreak\par
  329. Во время работы этих программ ведётся подробный диагностический журнал в файле
  330. \verb|oclh_*r.log| (соответственно названию программы), куда сохраняется
  331. избыточная информация обо всех доступных устройствах GPGPU, используемых
  332. платформах и созданных для сборки контекстах. Фактически вы можете запустить,
  333. например, \verb|oclh_сr| с любым входным файлом, даже с самим собой, как
  334. \verb|./oclh_сr oclh_сr|. Файл, конечно, не будет собран в объект OpenCL, но в
  335. журнале \verb|oclh_сr.log| останется полная информация по устройствам GPGPU,
  336. найденным в системе. Формат журнала человекочитаем, адаптирован к поиску
  337. подстрок с использованием команды \verb|grep| и аналогов. Формат файла-журнала
  338. описан в п.\ref{subsec:logformat}.
  339. Рассмотрим варианты использования каждой из этих программ.
  340. \input{tools_compiler-russian.tex}
  341. \input{tools_linker-russian.tex}
  342. \input{tools_builder-russian.tex}
  343. \section{Использование библиотеки OpenCL\_helpers. Структуры, функции и
  344. заголовки}
  345. \label{sec:libraryusing}
  346. Заглушка. Раздел будет оформлен после достаточного тестирования
  347. функциональности.
  348. \subsection{Структуры}
  349. \label{subsec:structures}
  350. Заглушка. Раздел будет оформлен после достаточного тестирования
  351. функциональности.
  352. \subsubsection{Основная структура рабочей конфигурации}
  353. \label{subsec:workset}
  354. Заглушка. Раздел будет оформлен после достаточного тестирования
  355. функциональности.
  356. \subsection{Функции журналирования}
  357. \label{subsec:logfunctions}
  358. Заглушка. Раздел будет оформлен после достаточного тестирования
  359. функциональности.
  360. \subsection{Общие для CPU и GPGPU кода заголовочные файлы}
  361. \label{subsec:sharedheaders}
  362. Заглушка. Раздел будет оформлен после достаточного тестирования
  363. функциональности.
  364. \section{Управление памятью и реинтерпретация указателей в программах OpenCL C}
  365. \label{sec:memalloc}
  366. Заглушка. Раздел будет оформлен после достаточного тестирования
  367. функциональности.
  368. \section{Параллелизм внутри GPU}
  369. \label{sec:squadmodel}
  370. Заглушка. Раздел будет оформлен после достаточного тестирования
  371. функциональности.
  372. \let\originalstyle=\thispagestyle
  373. \def\thispagestyle#1{}
  374. \printindex
  375. \let\thispagestyle=\originalstyle
  376. \tableofcontents
  377. \end{document}