momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

TeX şi MetaPost, pe o trisectoare Pascal

LaTex | MetaPost | Metafont
2018 aug

Când browser-ul are de redat pe ecranul utilizatorului un "document HTML" şi întâlneşte aici un element <img src="...">, el va rezerva o anumită zonă (sau "boxă") în locul curent al paginii în curs de constituire, va cere serverului fişierul indicat în atributul "src" şi obţinându-l, va executa cumva instrucţiunile grafice primite, încastrând imaginea corepunzătoare în boxa rezervată.

Lucrurile decurg în mod analog, când producem un document PDF dintr-un fişier ".tex" folosind pdflatex (sau alt compilator de LaTeX): întâlnind o comandă precum \includegraphics{...} (analogă elementului HTML <img>), compilatorul va căuta pe disk şi va încărca fişierul grafic indicat, redând imaginea respectivă în boxa corespunzătoare din pagină. Să recunoaştem că parcă este mai avantajos să avem documentul în format PDF (care încorporează deja imaginile necesare), decât în format "text/html" (care pretinde prezenţa fişierelor grafice vizate).

Crearea graficelor direct în pagina HTML este posibilă, prin specificaţia SVG (sau mai "clasic", folosind elemente HTML5 <canvas> asociate cu secvenţe de program javaScript). Iar în cazul fişierelor LaTeX, am putea folosi diverse pachete prin care să creem ad-hoc, figurile necesare (a vedea de exemplu, tikZ).

Aici ne propunem să recreem figurile din [1] în cadrul unui fişier LaTeX, folosind MetaPost (MP); se va vedea că de fapt, am adaptat şi am completat programul MetaFont din [1], organizând astfel lucrurile încât să obţinem într-o aceeaşi instanţă 'currentpicture' cele trei grafice:
(a) curba - anume, trisectoarea lui Pascal - trasată "punct cu punct",
(b) arcul trasat prin operatorul "path_join" plecând de la 6 anumite puncte ale curbei,
(c) figura produsă prin "suprapunerea" graficelor (b) şi (a).

Fişierul rezultat în final este figabc.tex; mai jos disecăm "pas cu pas" constituirea acestuia, pentru a evidenţia diverse aspecte de lucru cu LaTeX şi cu MetaPost.

Prevedem următorul preambul:

\documentclass[12pt]{article}
\usepackage{fontspec}
\usepackage[metapost={-interaction=nonstopmode}]{mpgraphics}
\configure[mpggraphic][linecolor=black]
\configure[mpggraphic][linewidth=.2pt]
\configure[mpggraphic][rulesep=8pt]

Intenţionăm să compilăm prin xelatex, încât incluzând pachetul fontspec nu va fi nevoie (ca în cazul lui pdflatex) să folosim comenzi TeX de producere a literelor cu accent - fiind recunoscută maniera de folosire a tastaturii pe care am instituit-o la nivelul siste­mului Ubuntu-Linux (literele cu accent ca 'ă', 'ş', etc. pot fi tastate direct, "prefixând" tastarea literei subiacente (fără accent) cu acţionarea tastei 'Alt'-dreapta).

Pachetul mpgraphics permite încorporarea de cod MP direct în fişierul LaTeX, asigurând că xelatex va include automat figura corespunzătoare, în documentul PDF creat pentru acest fişier. Cu opţiunea "metapost={-interaction=nonstopmode}" vom avea un ecou informativ ajutător –pe ecran sau/şi în fişierul ".log" produs la compilare–  în cazul unor eventuale erori strecurate în cursul dezvoltării codului MP respectiv.
Intenţionăm să conturăm un cadru dreptunghiular în jurul figurii pe care urmează să o constituim (sugerând că (a), (b) şi (c) sunt reunite într-o aceeaşi figură) şi de aceea, am configurat culoarea, grosimea liniei şi grosimea zonei de separare faţă de figură, a cadrului viitor.

Pachetele TeX au asociată o anumită documentaţie, accesibilă fie invocând de pe linia de comandă texdoc mpgraphics, fie căutând în catalogul CTAN. Pachetul "mpgraphics" a fost realizat de "Persian TeX Group" şi documentaţia aferentă are un aspect distinctiv foarte interesant: începe cu o dedicaţie "to the Iranian mathematician, Jamshid Kashani" - nimeni altul decât celebrul Al-Kashi (de la care ştim că se trage denumirea de "algoritm") - şi conţine o mică (şi instructivă) biografie. Ulugh Beg era un matematician şi astronom entuziast şi ajuns sultan - şi-a orientat politica pe dezvoltarea ştiinţei şi culturii, transformând Samarkand-ul într-un centru intelectual marcant; în aceste condiţii favorizante, Al-Kashi a reuşit să-şi pună în valoare talentul şi capacităţile creatoare (…instructiv, nu? Istoria ne dezvăluie că dezvoltarea ştiinţei, culturii şi învăţământului depinde mult de calităţile şi interesele celor care diriguiesc societatea…).

Într-o primă aproximare, documentul ar avea următorul conţinut:

\begin{document}
\centerline {\large Trisectoarea lui Pascal $w=z(z+1),\,z\in\mathcal{C},\,|z|=1$}

\begin{mpdisplay}
   %% urmează să completăm cu secvenţa de cod MetaPost, pentru figuri
\end{mpdisplay}

\noindent În cazul (a) curba este construită "punct cu punct".\\
În (b) am indicat anumite 6 puncte şi am folosit operatorul MF {\em path\_join}
{\small(interpolează între puncte, prin arce de curbe Bézier)}.\\
În (c) am suprapus construcţia (b) peste cea din (a).

\end{document}

Anturajul {mpdisplay} asigură centrarea orizontală (în cadrul paginii PDF finale) a figurii constituite prin codul MP subiacent.

MP ne permite să plasăm etichete, în cadrul figurii (în timp ce MetaFont permite etichete doar în modul proof). Am avea de etichetat cele 6 puncte, pe figura (b); în acest scop prevedem un font adecvat (dintre cele disponibile):

defaultfont := "pcrr8r";  % corespunde fontului 'Courier'

Mai departe, preluăm din [1] (cu mici îmbunătăţiri) construcţia punctelor care definesc figura (a):

vardef limacon(expr Z) = Z zscaled (Z + right) enddef;  % = z(z+1), z=x+iy

pair k[];
for fi=0 step 0.5 until 360:
    k[fi] := limacon(dir fi);  % limacon( (cosd fi, sind fi) );
endfor

Repetăm şi aici: U zscaled V interpretează punctele U şi V ca numere complexe şi produce ca rezultat punctul de afix U*V; în cazul nostru, se obţin produsele de numere complexe z şi z+1, care pentru |z|=1 (adică pentru punctele cercului unitate, obţinute prin dir<unghi>) sunt situate pe o aceeaşi trisectoare Pascal.

Pentru a obţine figura anunţată (a), nu rămâne decât să plotăm punctele k[], în număr de 720 - dovedit suficient de mare pentru ca (la o scară potrivită) să avem impresia optică de contur chiar şi fără a mai implica (precum în [1]) operatorul suplimentar "--" (unind prin segmente artificiale câte două puncte vecine); dar trebuie să ţinem seama şi acum, de o anumită "unitate de măsură".
De exemplu, am putea defini "U := 1cm", exprimând apoi toate coordonatele în centimetri (astfel, z1=(2U, 3U); draw(z1); va plota punctul aflat la 3 centimetri deasupra bazei orizontale, mai la dreapta cu 2cm faţă de marginea stângă a paginii). Totuşi, dat fiind că aici avem de creat o figură obişnuită (şi nu un caracter dintr-un anumit font, cum am avut în final în [1]), preferăm să ne bazăm pe unitatea de măsură implicită; în MP aceasta este 1bp, definită ca 1/72 dintr-un inch (ceva mai mare decât cea specifică pentru MF 1pt, cu valoarea de 1/72.27 inch).

Macro-ul următor ne va permite formulări unitare (şi comode) pentru operaţii de scalare şi deplasare asupra punctelor sau contururilor:

def scaleandshift(expr N) = scaled 25 shifted (N, 0) enddef;

De exemplu, draw (2,3) scaleandshift(1cm) withcolor red; va plota un "punct" roşu, în aceeaşi poziţie ca şi punctul z1 din exemplul anterior (de observat că punctul iniţial (2,3)≡(2bp, 3bp) apare prea aproape faţă de origine, ceea ce nu ne-ar conveni într-o figură).

Vom putea seta parametrul N corespunzător fiecăreia dintre cele trei figuri pe care le avem de încorporat în currentpicture; de exemplu, odată constituit conturul bpath pentru figura (b), îl vom obţine şi în (c) prin draw bpath scaleandshift(N), cu o anumită valoare N.

Următorul macro plotează punctele k[], folosind o peniţă pătrată:

def plotbypoints(expr N) =  % plotează punctele curbei, în (a) sau (c)
    pickup pensquare scaled .6;
    for i=0 step 0.5 until 360:
        draw k[i] scaleandshift(N);
    endfor
enddef;

plotbypoints(10) va produce conturul punctat din (a), iar plotbypoints(240) îl va reproduce în figura (c). Am preferat să "reapelăm" funcţia respectivă; alternativa este mai elegantă, dar nu neapărat mai bună şi constă în a salva într-o variabilă de tip 'picture' "conturul" din (a) (obţinut în 'currentpicture' după prima execuţie a macro-ului), urmând ca pentru (c) să adăugăm valoarea salvată (scalând-o corespunzător poziţiei lui (c) în cadrul întregii figuri).

Să presupunem că am constituit o variabilă q de tip "path", prin operatorul '..' (denumit sugestiv "path_join") pentru un număr de length(q) noduri (indicate în prealabil). Prin Z := point t of q  vom obţine în variabila Z (presupusă de tip "pair") nodul de rang t de pe conturul q; prin direction t of q  obţinem un vector tangent arcului de curbă respectiv în punctul de rang t al conturului (şi rotindu-l cu 90° obţinem un vector normal arcului, orientat fie spre exteriorul acestuia, fie spre interior). Cu aceste elemente putem imagina un procedeu de etichetare uniformă, a nodurilor respective - aşezând etichetele (prin macro-ul MP label()) pe direcţiile normalelor la curbă în punctele respective, toate spre exterior sau toate spre interior:

def marks(expr L) =  % etichetează uniform nodurile de bază ale unui contur
    pickup pencircle scaled 2.5; pair Z;
    for t=0 upto length(q):  % 'q' fiind de tip "path", cu ".." (path_join)
        Z := point t of q; 
        draw Z;  % plotează punctul de rang 't' al conturului
        if(L):  % etichetează exterior, pe direcţia normalei (dacă L=true)
            label(decimal(t+1), 7 unitvector(direction t of q) 
                                rotated(-90) shifted Z) withcolor blue;
        fi;
    endfor
enddef;

decimal() transformă "numeric" în "string"; unitvector() produce versorul corespunzător aici vectorului normal şi l-am "lungit" de 7 ori pentru a depărta puţin eticheta, faţă de curbă. Vom folosi marks(true) pentru a marca şi eticheta cele 6 noduri ale arcului de curbă din (b); însă pentru (c) vom folosi marks(false), marcând şi aici nodurile, dar renunţând la etichetarea acestora.

Mai departe, preluăm din [1] definiţiile punctelor şi conturului pentru (b):

z1 = ((-1+sqrt(33))/16, sqrt((207+33*sqrt(33))/2)/8);  % punctul cel mai înalt
z2 = ((-1-sqrt(33))/16, sqrt((207-33*sqrt(33))/2)/8);
z3 = z2 reflectedabout((0,0), (2,0));
atan2 = angle(1, 2) + 180;  % panta tangentei în (-1,1) este arctg(2)
path bpath, q;  % în 'q' vom scala după caz 'bpath'
bpath := (2,0){up}..z1{left}..(-1,1){dir atan2}..(-1,0)..z3{right}..{up}(0,0);

Vom vrea în final să etichetăm cele trei grafice, dedesubtul acestora, respectiv prin "(a)", "(b)" şi "(c)", folosind alt font decât cel prin care am marcat cele 6 noduri. Punctul z1 este punctul cel mai înalt al curbei şi simetrizându-l (prin reflectedabout()) faţă de axa absciselor, obţinem linia dedesubtul căreia vrem să aşezăm etichetele graficelor; folosim substring() pentru a extrage eticheta necesară din şirul "(a)(b)(c)" şi infont pentru a selecta un anumit font:

def figlabels =  % etichetarea celor trei grafice
    z5 = z1 reflectedabout((0,0), (2,0));
    for i=0 upto 2:
        label.bot(substring(3i,3*(i+1)) of "(a)(b)(c)" infont "ptmr8r" scaled 1.1, 
                  z5 scaleandshift(10+i*115) + (0, -5));
    endfor
enddef;

Programul "principal" este acum simplu de construit:

plotbypoints(10);  % trasează conturul din (a) (trisectoarea lui Pascal)

q := bpath scaleandshift(125);  % translatează 'bpath' cu 125bp≈1.7in
pickup pencircle; 
draw q withcolor blue;  % obţine arcul de curbă din (b)
marks(true);  % marchează şi etichetează cele 6 noduri

plotbypoints(240);  % trasează conturul din (a), la 240bp faţă de marginea stângă
q:= bpath scaleandshift(240);  % deplasează 'bpath' spre dreapta, cu 240bp
pickup pencircle; draw q withcolor .8blue;  % trasează 'bpath' în noua poziţie
marks(false);  % marchează cele 6 noduri, în (c)

figlabels;  % etichetează cele trei grafice rezultate    %% \end{mpdisplay}

Este drept însă, că în realitate dezvoltarea a decurs mai degrabă în sens invers - imaginând întâi poza finală cu (a), (b) şi (c), schiţând apoi acest "program principal" şi constituind după aceea elementele pe care mai sus, le-am prezentat "logic", înainte de a le angaja împreună în final.

Nu rămâne decât să compilăm fişierul astfel întregit (desigur, am făcut aceasta de mai multe ori şi până a ajunge în acest punct "final" - îndreptând mereu diverse aspecte şi erori):

vb@Home:~/4_art/Latex/aVB/11$ xelatex -shell-escape  figabc.tex

iar rezultatul este următorul fişier PDF:

Mesajele informative postate în cursul procesului de compilare dezvăluie cum decurg lucrurile:

/* ... */
Package mpgraphics Warning:
(mpgraphics)   Using \write 18 capability for producing PDF-figures. 
/* ... */
Opening MPGStream=figabc-fig1.mp
This is MetaPost, version 2.000 (TeX Live 2017/Debian) (kpathsea version 6.2.3)
(/usr/share/texlive/texmf-dist/metapost/base/mpost.mp
/* ... */
1 output file written: figabc-fig1.eps
Transcript written on figabc-fig1.log.
 [1] (./figabc.aux) )
Output written on figabc.pdf (1 page).

Opţiunea de compilare shell-escape a activat facilitatea "\write18" oferită de către TeX. \write<4-bit number> este o comandă obişnuită, pentru a scrie într-un anumit fişier (deschis în prealabil şi având asociat un index numeric 0..15); compilatorul o foloseşte de exemplu pentru a scrie în fişierele ".log" şi ".aux". Însă \write18 "scrie" direct sistemului de operare (printr-un anumit "stream", asociat liniei de comandă), permiţând execuţia oricărei comenzi recunoscute de către acesta (de aceea şi este dezactivată iniţial, fiind de folosit numai în cunoştinţă de cauză).

Mesajele redate mai sus s-ar interpreta cam aşa: se deschide un "stream" către sistemul de operare ("MPGStream", prevăzut de pachetul mpgraphics), se copiază în acest stream codul MP existent în "figabc.tex" (între /begin{mpdisplay} şi \end{mpdisplay}), apoi (prin write18) se lansează compilatorul de MetaPost mpost, transformând codul MP existent pe stream în instrucţiuni PostScript "figabc-fig1.eps". Apoi, se foloseşte ceva de genul \write18{epstopf "figabc-fig1.eps"}, lansând programul 'epstopdf' pentru a converti fişierul temporar ".eps" rezultat mai sus, în format PDF (scris pe disk, în "figabc-fig1.pdf"); figura în format PDF rezultată astfel este apoi încorporată în locul cuvenit, în fişierul final "figabc.pdf".

Să observăm că dacă ar fi să construim figura obţinută mai sus folosind SVG (pentru o pagină HTML), atunci –pentru (b)– ne-am lovi de o problemă dificilă; în SVG dispunem de un operator analog cu "path_join" din MetaFont (bazat deasemenea, pe cubice Bézier), dar cu o diferenţă esenţială: MetaFont (destinat creaţiei "automate" de caractere) determină el însuşi "punctele de control" necesare, în timp ce în SVG (orientat mai degrabă pe interacţiunea cu mouse-ul utiliza­torului) acestea trebuie precizate în mod explicit în definiţia conturului respectiv. În plus, în MF putem folosi ecuaţii obişnuite pentru a defini diverse puncte în raport cu unele cunoscute deja (MetaFont "ştie" să rezolve ecuaţii), pe când în SVG nu. Desigur, aceste diferenţe majore derivă din faptul că sistemul TeX în ansamblul său este destinat în fond unui singur utilizator - autorul documentului - pe când SVG vizează cerinţele de interacţiune ale utilizatorilor potenţiali de pe Internet, ai aplicaţiei subiacente.

vezi Cărţile mele (de programare)

docerpro | Prev | Next