Francois a écrit :
Bonjour,
J'utilise LaTeX assez r�guli�rement et je fais souvent des dessins avec
PSTricks. Je me demandais ce que tu cherchais � faire comme dessin
exactement et en quoi Python intervient l� dedans.
Serait-il possible d'avoir des petits pr�cisions ? PSTricks � lui seul
ne suffit pas ? Pourquoi fais-tu intervenir Python ? �a m'int�resse.
Désolé pour la réponse tardive.
Je suis en train de taper un cours de math et de physique pour nos
têtes blondes.
http://www.enseignons.be/secondaire/preparations-34-mecanique-optique-relativite-4262.html
Les sources LaTeX sont ici
http://student.ulb.ac.be/~lclaesse/echa.tar.gz
J'ai déjà tapé beaucoup en pstricks, et j'avoue que la syntaxe ne me
plaît pas; la géométrie est réellement plus maniable en orienté
objet : un segment est une classe qui a une méthode "milieu" qui est
de la classe "Point", etc.
Je suis en train de taper un chapitre sur les dérivées, et ce que je
veux faire, c'est ceci.
On prend une courbe F, et on y met un point P fixe. Ensuite, un point
Q se rapproche de P et on trace la ligne PQ. Lorsque Q se rapproche de
P, la ligne devient la tangente. Et chaque ligne a une couleur
différente. Le tout aussi automatisé que possible.
Je suis parvenu à faire ça en pstricks avec des multido, mais ça a été
la folie !
Attention au code :
++++++++++++++++ Exemple de code pstricks +++++++++++++++++
\documentclass[a4paper,12pt]{book}
\usepackage{latexsym}
\usepackage{amsfonts}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{bbm}
\usepackage{cases}
\usepackage{eso-pic}
\usepackage{pstricks}
\usepackage{pst-node}
\usepackage{pst-eucl}
\usepackage{pst-plot}
\usepackage{pst-math}
\usepackage{pstricks-add}
\usepackage{multido}
\usepackage{fp}
\usepackage{ifthen}
\usepackage{subfigure}
\usepackage{graphicx}
% La macro ValeurFonction
% #1 est le nom de la fonction à calculer. Cette dernière doit être
une macro à un argument du genre 3*#1+5/#1
% #2 est la valeur de la variable en laquelle on veut calculer la
fonction
% #3 est le nom de la variable FP dans laquelle le résultat sera
écrit.
\newcommand{\ValeurFonction}[3]{\FPeval{#3}{#1{#2}}}
% La macro pstPointSurCourbe
% #1 est le nom de la fonction
% #2 est le x où l'on veut le point
% #3 est le nom du point pstricks qui sera en x sur la courbe.
\newcommand{\pstPointSurCourbe}[3]{%
\ValeurFonction{#1}{#2}{inter}
\pstGeonode(#2,\inter){#3}
}
% La macro \pstMarquePoint prend les arguments suivants :
% - 1 est optionnel : il donne le style de point qui va arriver
% - 2 est le point (au sens pstrick) qui doit être marqué
% - 3 est la position où va se trouver la marque par rapport au point
pstrick. Typiquement c'est (0.3;90) en coordonnées polaires
% - 4 est ce qu'on va y noter
\newcommand{\pstMarquePoint}[4][PointSymbol=none]{%
\rput(#2){\rput(#3){#4}} % Mettre le symbole du point là où il doit
être
\pstGeonode[#1](#2){mpinter} % Cette ligne fait apparaître un point
à l'endroit que l'on marque.
% étant donné le \psset{PointSymbol=none} dans lequel toutes
les figures se trouvent,
% ceci ne fait pas grand chose si on ne donne pas Ã
\pstMarquePoint l'argument optionnel
% du type PointSymbol=* par exemple.
}
% La macro pstLigneLongueur
% #1 Optionnel : ce sont les parametres de tracé
% #2 Premier point de la ligne
% #3 Second point de la ligne
% #4 Longueur de la ligne souhaitée
%
% Cela trace une droite de longueur spécifiée qui passe par les deux
points donnés.
\newcommand{\pstLigneLongueur}[4][]{%
\pstMiddleAB{#2}{#3}{interC} % Trouver le milieu du segment
donné
\pstInterLC[Diameter=\pstDistVal{#4}]{#2}{#3}{interC}{}{interP}
{interQ} % Points d'intersection entre la droite et le cercle de
diamètre #4
\pstLineAB[#1]{interP}{interQ}
}
\begin{document}
%
% L'environement ParamsTanApproxDerr contient les paramètres des
figures à tracer pour mon approx de dérivation
% L'environement FigTanApproxDerr est l'environement de figure elle-
même. Elle est censée se trouver à l'intérieur de ParamsTanApproxDerr.
%
% \Fn est la fonction au sens où \Fn{a} donne l'expression de la
fonction avec a. Pour calculer la valeur de la fonction en 4, il faut
donner \Fn{4} Ã FPeval. C'est ce que fait la macro \ValeurFonction.
% \psFn est la fonction à donner à \psplot. \psFn est l'expression de
Fn donnée avec x.
\newenvironment{ParamsTanApproxDerr}[1]{%
\newcommand{\Fn}[1]{(-3)/(##1)+5} %-3/x + 5
\newcommand{\psFn}{\Fn{x}}
\FPeval{Px}{1}
\FPeval{Qx}{8}
% Les deux macros suivantes donnent les coordonnées où doivent être
placés les labels des points Q0, Q1, ... Y'a rien à faire : ils
doivent être mis à la main.
\newcommand{\CoorR}[1]{
\ifnum ##1=0 0.3\fi
\ifnum ##1=1 0.3\fi
\ifnum ##1=2 0.3\fi
\ifnum ##1=3 0.5\fi
\ifnum ##1=4 0.5\fi
\ifnum ##1=5 0.5\fi
\ifnum ##1=6 0.5\fi
\ifnum ##1=7 0.5\fi
}
\newcommand{\CoorA}[1]{%
\ifnum ##1=0 -90\fi
\ifnum ##1=1 -90\fi
\ifnum ##1=2 -90\fi
\ifnum ##1=3 -90\fi
\ifnum ##1=4 -90\fi
\ifnum ##1=5 -90\fi
\ifnum ##1=6 -45\fi
\ifnum ##1=7 -45\fi
}
% La macro qui suit donne la couleur du tracé de l'approximation de
tangente.
% Ici c'est important de ne pas mettre d'espace entre la couleur et
le \fi, sinon cet espace est prit comme faisant partie du nom de la
couleur
% et alors xcolor se plaint de ne pas la connaître.
% typiquement on a ceci : ! Package xcolor Error: Undefined color
`red '.
\newcommand{\CoulTan}[1]{%
\ifnum ##1=0 blue\fi
\ifnum ##1=1 blue\fi
\ifnum ##1=2 blue\fi
\ifnum ##1=3 blue\fi
\ifnum ##1=4 blue\fi
\ifnum ##1=5 blue\fi
\ifnum ##1=6 blue\fi
\ifnum ##1=7 blue\fi
}
% J'ai besoin de la bounding box en vrai et en échelle parce que les
axes sont tracés après avoir imposé l'échelle, ce qui fait que, eux,
doivent être donnés par les nombres non mis à l'échelle.
% La bounding box est (-0.9,-1,1)(11,5)
\FPeval{BBbgx}{(-0.9)}
\FPeval{BBbgy}{(-1.1)}
\FPeval{BBhdx}{10}
\FPeval{BBhdy}{5}
% Je la recalcule en fonction de l'échelle donnée
\FPeval{eBBbgx}{0+\BBbgx*#1}
\FPeval{eBBbgy}{0+\BBbgy*#1}
\FPeval{eBBhdx}{0+\BBhdx*#1}
\FPeval{eBBhdy}{0+\BBhdy*#1}
\newenvironment{FigTanApproxDerr}{
\begin{pspicture}(\eBBbgx,\eBBbgy)(\eBBhdx,\eBBhdy)
\psset{algebraic=true, PointSymbol=none, PointName=none,
xunit=#1cm, yunit=#1cm}
% Mettre les points sur la courbe
\pstPointSurCourbe{\Fn}{\Px}{P}
\pstPointSurCourbe{\Fn}{\Qx}{Q}
% Définir les vecteurs de base de mon système de coordonnées
\pstGeonode(0,0){O}(1,0){X}(0,1){Y}
% Construire le point I qui est à l'angle du rectangle donné par P
et Q.
\pstTranslation{O}{X}{P}[PX]
\pstTranslation{O}{Y}{Q}[QY]
\pstInterLL{P}{PX}{Q}{QY}{I}
% Placer les points où les Delta x et Delta y vont être mit
\pstMiddleAB{P}{I}{Del}
\pstMiddleAB{Q}{I}{ff}
% Dessiner les axes et tracer la courbe.
\psaxes[dotsep=1pt]{->}(0,0)(\BBbgx,\BBbgy)(\BBhdx,\BBhdy)
\psplot{0.5}{9}{\psFn}
} % Fin de l'ouverture de l'environnement FigTanApproxDerr
{\end{pspicture}} % Fin de la fermeture de l'environnement
FigTanApproxDerr
} % Fin de l'ouverture de l'environement ParamsTanApproxDerr
{} % Fin de la fermenture de l'environement ParamsTanApproxDerr
\begin{center}
\begin{ParamsTanApproxDerr}{0.5}
\begin{FigTanApproxDerr}
\pstMarquePoint[PointSymbol=*]{P}{0.3;0}{$P$}
\end{FigTanApproxDerr}
\end{ParamsTanApproxDerr}
\end{center}
\begin{figure}[ht]
\centering
\begin{ParamsTanApproxDerr}{0.5}
\FPeval{NumPoints}{8} % Note qu'avec n points, ils sont numérotés
de 0 Ã n-1, comme le veux une vieille tradition en informatique.
\FPeval{Intervalle}{(\Qx-\Px)/\NumPoints}
\multido{\n=0+1}{\NumPoints}{%
\subfigure{
\begin{FigTanApproxDerr}
\FPeval{Qix}{\Qx-(\n*\Intervalle)}
\pstMarquePoint[PointSymbol=*]{P}{0.3;145}{$P$}
\pstPointSurCourbe{\Fn}{\Qix}{Qi\n}
\pstMarquePoint[PointSymbol=*]{Qi\n}{\CoorR{\n};\CoorA{\n}}
{$Q_{\n}$}
\FPeval{Qix}{\Qix+1}
\pstLigneLongueur[linecolor=\CoulTan{\n}]{P}{Qi\n}{7}
\end{FigTanApproxDerr}
} % Fin de la sous-figure.
} % Fin du multido
\end{ParamsTanApproxDerr}
\caption{Plus le second point d'intersection avec la courbe s'approche
de $P$, plus la droite ressemble à la tangente.}
\label{FigTanApproxSuite}
\end{figure}
\end{document}
+++++Fin Exemple d'utilisation pstricks ++++++++++++++++++
Après avoir fait ça, j'avoue que j'en ai eut marre et j'ai commencé Ã
programmer en python des classes qui permettent de produire les code
pstricks.
Avantages :
* Calcul automatique de la bounding box
* orienté objet
* La programmation est plus simple (boucles for, calculs, utilisations
de variables, ...). Parce que programmer en LaTeX ... heu ...
Inconvénients :
* Deux "compilations" (lancer le scipt, puis compiler avec LaTeX)
* Obligation d'avoir python sur son ordi (je crois que ça vient de
base avec tous les OS sauf Gentoo, non ? --- wooo putain, le
troll !!! )
* Utilisation de maxima pour calculer des intersections (pas installé
d'office avec tous les os)
* ... ??? Je verrai à l'usage si il y a d'autres inconvénients
Voici le script qui me fournit le code pstricks d'à peu près les mêmes
figures.
+++++++++++ Code python +++++++++++++++
#! /usr/bin/python
# -*- coding: utf8 -*-
from __future__ import division
import commands, os, copy # La classe copy est pour faire des
copies d'objets
# Ce script demande aussi la présence de Maxima sur le système,
# pour calculer des extremas de fonctions, entre autres.
REP = commands.getoutput("pwd")
class Fichier(object):
def __init__ (self, filename):
self.NomComplet = filename
self.chemin = self.NomComplet
def OpenFile(self,opt):
self.file = open(self.NomComplet,opt) # Pour fermer le fichier,
c'est self.file.close()
class ListeNomsPoints(object):
def __init__(self):
self.donne = [97,97,96]
def suivant(self):
self.donne[2] = self.donne[2]+1
if self.donne[2] == 123:
self.donne[2] = 97
self.donne[1] = self.donne[1] + 1
if self.donne[1] == 123:
self.donne[1] = 97
self.donne[0] = self.donne[0] + 1
return chr(self.donne[0])+chr(self.donne[1])+chr(self.donne[2])
NomPointLibre = ListeNomsPoints()
class maxima(object):
def calcul(self,ligne):
sortie = commands.getoutput( "maxima --batch-string\"display2d:false;\n float("+ligne+");\""+"|grep o2" )
return sortie
def solve(self,eq,vars):
return self.calcul( " solve(["+eq+"],["+vars+"]) " )
def extrema(self,f):
sortie = self.solve("diff("+f.fx
+",x)=0","x").replace(",","").replace("]","")
a = sortie.split("x =")
del a[0]
return a
class BoundingBox(object):
def __init__(self,dbg,dhd):
self.bg = dbg
self.hd = dhd
def AddPoint(self,P):
self.bg = Point( min(self.bg.x,P.x), min(self.bg.y,P.y) )
self.hd = Point( max(self.hd.x,P.x), max(self.hd.y,P.y) )
class Point(object):
def __init__(self,x,y):
self.x = x
self.y = y
self.psNom = NomPointLibre.suivant()
def Coord(self):
return "("+str(self.x)+","+str(self.y)+")"
def CoordBr(self):
return "{"+str(self.x)+","+str(self.y)+"}"
class Segment(object):
def __init__(self,A,B):
self.I = A
self.F = B
self.vertical = 0
if A.x == B.x :
self.vertical = 1
if self.vertical == 0:
self.equation = [(A.y-B.y)/(A.x-B.x),(A.y*B.x-A.x*B.y)/(A.x-B.x)]
def Milieu(self):
return Point( (self.I.x+self.F.x)/2, (self.I.y+self.F.y)/2 )
class Rectangle(object):
def __init__(self,A,B):
self.bg = A
self.hd = B
self.bd = Point( self.hd.x,self.bg.y )
self.hg = Point( self.bg.x,self.hd.y )
class Fonction(object):
def __init__(self,fun):
self.fx = fun
def eval(self,x):
return eval(self.fx.replace("x",str(x)))
def PointDessus(self,x):
return Point(float(x),self.eval(x))
def extremas(self):
l = []
for x in maxima().extrema(self):
l.append(Point(x),self.eval(x))
return l
class Cercle(object):
def __init__(self,C,r):
self.Centre = C
self.Rayon = r
# Une figure est le but ultime de ce script. Une figure est une suite
de subfigures, lesquelles sont destinées à être essentiellement des
pspictures.
class figure(object):
def __init__(self,caption,label,fich):
self.caption = caption
self.label = label
self.xunit = 1
self.yunit = 1
self.code = []
self.SSfigures = []
self.fichier = Fichier (fich)
self.AjouteLigne("\\begin{figure}[ht]")
self.AjouteLigne("\centering")
def Conclure(self):
self.AjouteLigne("\psset{xunit="+str(self.xunit)
+",yunit="+str(self.yunit)+"}\n")
for f in self.SSfigures :
self.AjouteLigne("\subfigure["+f.caption+"]{%")
self.AjouteCode(f.code)
self.AjouteLigne("} % Fermeture de la sous-figure
"+str(self.SSfigures.index(f)+1))
self.AjouteLigne("%")
self.AjouteLigne("\caption{"+self.caption+"}\label{"+self.label+"}")
self.AjouteLigne("\end{figure}")
self.contenu = "".join(self.code)
def Dilate(self,fact):
self.xunit = self.xunit * fact
self.yunit = self.yunit * fact
def AjouteSSfigure(self,ssFig):
self.SSfigures.append(ssFig)
def AjoutePspicture(self,fig):
self.AjouteLigne(fig.contenu())
def AjouteLigne(self,ligne):
self.code.append(ligne+"\n")
def AjouteCode(self,liste_code):
self.code.extend(liste_code)
def EcrireFichier(self):
self.fichier.OpenFile("w")
self.fichier.file.write(self.contenu)
self.fichier.file.close()
# Le \subfigure[caption]{ ne se met pas dans le code de la classe
subfigure parce que dans la classe figure, je numérote les sous-
figures.
# Typiquement, une sousfigure sera juste créée en ajoutant une
pspicture d'un coup, et puis c'est tout.
class subfigure(object):
def __init__(self,caption,label):
self.caption = caption
self.label = label
self.code = []
def AjouteLigne(self,ligne):
self.code.append(ligne)
def AjouteCode(self,cod):
print "Mon code est"
print self.code
print "Et j'ajoute"
print cod
self.code.extend(cod)
def AjoutePspicture(self,psp):
self.AjouteLigne(psp.contenu())
class pspicture(object):
def __init__(self):
self.code = []
self.listePoint = []
self.AjouteLigne("\psset{PointSymbol=none,PointName=none,algebraic=true}
\n")
self.BB = BoundingBox(Point(1000,1000),Point(-1000,-1000))
def Dilate(self,fact):
self.xunit = self.xunit * fact
self.yunit = self.yunit * fact
def AjouteLigne(self,ligne):
self.code.append(ligne+"\n")
def AddFonction(self,fun,params,deb,fin):
self.BB.AddPoint( Point(deb,fun.eval(deb) ) )
self.BB.AddPoint( Point(fin,fun.eval(fin) ) )
for p in fun.extremas() :
self.BB.AddPoint(p)
self.AjouteLigne( "\psplot["+params+"]{"+str(deb)+"}{"+str(fin)+"}
{"+fun.fx+"}" )
def AddPoint(self,P,params):
self.AjouteLigne("\pstGeonode["+params+"]"+P.Coord()+"{"+P.psNom
+"}")
self.listePoint.append(P.psNom)
def MarquePoint(self,P,params,dist,angle,marque):
if P.psNom not in self.listePoint :
self.AddPoint(P,"PointSymbol=none,PointName=none")
self.BB.AddPoint(P)
self.AjouteLigne("\pstMarquePoint["+params+"]{"+P.psNom+"}
{"+str(dist)+";"+str(angle)+"}{"+marque+"}")
def AddSegment(self,seg,params):
self.BB.AddPoint(seg.I)
self.BB.AddPoint(seg.F)
self.AddPoint(seg.I,"")
self.AddPoint(seg.F,"")
self.AjouteLigne("\pstLineAB["+params+"]{"+seg.I.psNom+"}
{"+seg.F.psNom+"}")
def AddRectangle(self,rec,params):
self.BB.AddPoint(rec.bg)
self.BB.AddPoint(rec.hd)
self.AddSegment( Segment(rec.hg,rec.hd),params )
self.AddSegment( Segment(rec.hd,rec.bd),params )
self.AddSegment( Segment(rec.bd,rec.bg),params )
self.AddSegment( Segment(rec.bg,rec.hg),params )
def contenu(self):
a = ["\\begin{pspicture}"+self.BB.bg.Coord()+self.BB.hd.Coord()
+"\n"]
a.extend(self.code)
a.append("\end{pspicture}\n")
return "".join(a)
# Création de la pspicture générale.
##########################################################################
pspict_gene = pspicture()
f = Fonction("(-3/x)+5")
P = f.PointDessus(1)
Q = f.PointDessus(8)
angle = Point(Q.x , P.y)
pspict_gene.AddFonction(f,"",0.5,9)
pspict_gene.AddRectangle( Rectangle(pspict_gene.BB.bg,pspict_gene.BB.hd),"linecolor=cyan" )
# Création de la grande figure.
##########################################################################
pspict_gd = copy.deepcopy(pspict_gene)
pspict_gd.MarquePoint(P,"PointSymbol=*",0.3,180,"$P$")
pspict_gd.MarquePoint(Q,"PointSymbol=*",0.3,90,"$Q$")
pspict_gd.AddSegment(Segment(P,angle),"linestyle=dashed")
pspict_gd.AddSegment(Segment(Q,angle),"linestyle=dashed")
fig_gd = figure("Ma figure","leLabel",REP+"/figure1.tex")
fig_gd.AjoutePspicture(pspict_gd)
fig_gd.Conclure()
fig_gd.EcrireFichier()
# Création des sous figures
##########################################################################
n_ssfig = 6
ssfigs = []
for i in range(0,n_ssfig):
ssfig = subfigure("La suivante","UnLabel")
pspict = copy.deepcopy(pspict_gene)
Pi = f.PointDessus( Q.x-i*(Q.x-P.x)/(n_ssfig) )
pspict.AddSegment(Segment(P,Pi),"linecolor=blue")
ssfig.AjoutePspicture(pspict)
ssfigs.append(ssfig)
# Création de la figure principale
##########################################################################
fig_pr = figure("Ma figure principale","FigPrinc",REP+"/figure2.tex")
for ssf in ssfigs :
fig_pr.AjouteSSfigure(ssf)
fig_pr.Dilate(0.5)
fig_pr.Conclure()
fig_pr.EcrireFichier()
+++++++++++++++++++++++++++ Fin du code python +++++++++++++++++
C'est pas encore très portable, ni tout à fait terminé, mais je trouve
que c'est plus agréable à utiliser.
Tout commentaire sur le code ou sur l'idée de faire des classes en
python pour programmer du code LaTeX est le bienvenu (je peux même
commenter le code si il faut ;) )
Bonne soirée
Laurent