VIII. Exemples▲
VIII-A. Mean power (fonction, argparse)▲
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Exemple de script (shebang, docstring, etc.) permettant une
utilisation en module (`import mean_power`) et en exécutable (`python
mean_power.py -h`);
"""
from
__future__
import
division # Les divisions entre entiers ne sont pas euclidiennes
def
mean_power
(
alist, power=
1
):
"""
Retourne la racine `power` de la moyenne des éléments de `alist` à
la puissance `power`:
.. math:: \mu = (
\f
ra
c{1}
{N}\sum_{i=0}^{N-1} x_i^p)^{1/p}
`power=1` correspond à la moyenne arithmétique, `power=2` au *Root
Mean Squared*, etc.
Exemples:
>>>
mean_power
(
[1
, 2
, 3
])
2.0
>>>
mean_power
(
[1
, 2
, 3
], power=
2
)
2.160246899469287
"""
s =
0.
# Initialisation de la variable *s* comme *float*
for
val in
alist: # Boucle sur les éléments de *alist*
s +=
val **
power # *s* est augmenté de *val* puissance *power*
# *mean* = (somme valeurs / nb valeurs)**(1/power)
mean =
(
s /
len(
alist)) **
(
1
/
power) # ATTENTION aux divisions euclidiennes !
return
mean
if
__name__
==
'__main__'
:
# start-argparse
import
argparse
parser =
argparse.ArgumentParser
(
)
parser.add_argument
(
'list'
, nargs=
'*'
, type=
float, metavar=
'nombres'
,
help=
"Liste de nombres à moyenner"
)
parser.add_argument
(
'-i'
, '--input'
, nargs=
'?'
, type=
file,
help=
"Fichier contenant les nombres à moyenner"
)
parser.add_argument
(
'-p'
, '--power'
, type=
float, default=
1.
,
help=
"'Puissance' de la moyenne (
%d
efault)"
)
args =
parser.parse_args
(
)
# end-argparse
if
args.input: # Lecture des coordonnées du fichier d'entrée
# Le fichier a déjà été ouvert en lecture par argparse (type=file)
try
:
args.list =
[float(
x) for
x in
args.input
if
not
x.strip
(
).startswith
(
'#'
)]
except
ValueError
:
parser.error
(
"Impossible de déchiffrer la ligne '{}' du fichier '{}'"
.format
(
x, args.input))
# Vérifie qu'il y a au moins un nombre dans la liste
if
not
args.list:
parser.error
(
"La liste doit contenir au moins un nombre"
)
# Calcul
moyenne =
mean_power
(
alist, args.power)
# Affichage du résultat
print
"La moyenne des {} nombres à la puissance {} est {}"
.format
(
len(
alist), args.power, moyenne)
Source : mean_power.py
VIII-B. Formes (POO)▲
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Exemple de Programmation Orientée Objet.
"""
__author__
=
"Mathieu Leocmach <mathieu.leocmach@ens-lyon.fr>"
__version__
=
"Time-stamp: <2014-10-03 10:54 mathieu.leocmach@ens-lyon.fr>"
# Définition d'une classe ==============================
class
Forme
(
object): # *object* est la classe dont dérivent toutes les autres
"""Une forme plane, avec éventuellement une couleur."""
def
__init__
(
self, couleur=
None
):
"""Initialisation d'une Forme, sans couleur par défaut."""
if
couleur is
None
:
self.couleur =
'indéfinie'
else
:
self.couleur =
couleur
def
__str__
(
self):
"""
Surcharge de la fonction `str()`: l'affichage *informel* de
l'objet dans l'interpréteur, p.ex. `print a` sera résolu comme
`a.__str__()`
Retourne une chaîne de caractères.
"""
return
"Forme encore indéfinie de couleur {}"
.format
(
self.couleur)
def
change_couleur
(
self, newcolor):
"""Change la couleur de la Forme."""
self.couleur =
newcolor
def
aire
(
self):
"""
Renvoi l'aire de la Forme.
L'aire ne peut pas être calculée dans le cas où la forme n'est
pas encore spécifiée : c'est ce que l'on appelle une méthode
'abstraite', qui pourra être précisée dans les classes filles.
"""
raise
NotImplementedError
(
"Impossible de calculer l'aire d'une forme indéfinie."
)
def
__cmp__
(
self, other):
"""
Comparaison de deux Formes sur la base de leur aire.
Surcharge des opérateurs de comparaison de type `{self} <
{other}`: la comparaison sera résolue comme
`self.__cmp__(other)` et le résultat sera correctement
interprété.
.. WARNING:: cette construction n'est plus supportée en Python3.
"""
return
cmp(
self.aire
(
), other.aire
(
)) # Opérateur de comparaison
class
Rectangle
(
Forme):
"""
Un Rectangle est une Forme particulière.
La classe fille hérite des attributs et méthodes de la
classe -mère, mais peut les surcharger (i.e. en changer la
définition), ou en ajouter de nouveaux:
- les méthodes `Rectangle.change_couleur()` et
`Rectangle.__cmp__()` dérivent directement de
`Forme.change_couleur()` et `Forme.__cmp__()`;
- `Rectangle.__str__()` surcharge `Forme.__str__()`;
- `Rectangle.aire()` définit la méthode jusqu'alors abstraite
`Forme.aire()`;
- `Rectangle.allonger()` est une nouvelle méthode propre à
`Rectangle`.
"""
def
__init__
(
self, longueur, largeur, couleur=
None
):
"""
Initialisation d'un Rectangle longueur × largeur, sans couleur par
défaut.
"""
# Initialisation de la classe parente (nécessaire pour assurer
# l'héritage)
Forme.__init__
(
self, couleur)
# Attributs propres à la classe Rectangle
self.longueur =
longueur
self.largeur =
largeur
def
__str__
(
self):
"""Surcharge de `Forme.__str__()`."""
return
"Rectangle {}x{}, de couleur {}"
.format
(
self.longueur, self.largeur, self.couleur)
def
aire
(
self):
"""
Renvoie l'aire du Rectangle.
Cette méthode définit la méthode abstraite `Forme.area()`,
pour les Rectangles uniquement.
"""
return
self.longueur *
self.largeur
def
allonger
(
self, facteur):
"""Multiplie la *longueur* du Rectangle par un facteur"""
self.longueur *=
facteur
if
__name__
==
'__main__'
:
s =
Forme
(
) # Forme indéfinie et sans couleur
print
"s:"
, str(
s) # Interprété comme `s.__str__()`
s.change_couleur
(
'rouge'
) # On change la couleur
print
"s après change_couleur:"
, str(
s)
try
:
print
"Aire de s:"
, s.aire
(
) # La méthode abstraite lève une exception
except
NotImplementedError
as
err:
print
err
q =
Rectangle
(
1
, 4
, 'vert'
) # Rectangle 1×4 vert
print
"q:"
, str(
q)
print
"Aire de q:"
, q.aire
(
)
r =
Rectangle
(
2
, 1
, 'bleu'
) # Rectangle 2×1 bleu
print
"r:"
, str(
r)
print
"Aire de r:"
, r.aire
(
)
print
"r >= q:"
, (
r >=
q) # Interprété comme r.__cmp__(q)
r.allonger
(
2
) # r devient un rectangle 4×1
print
"Aire de r après l'avoir allongé d'un facteur 2:"
, r.aire
(
)
print
"r >= q:"
, (
r >=
q)
Source : formes.py
VIII-C. Cercle circonscrit (POO, argparse)▲
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Calcule le cercle circonscrit à 3 points du plan.
Ce script sert d'illustration à plusieurs concepts indépendants:
- un exemple de script (shebang, docstring, etc.) permettant une
utilisation en module (`import circonscrit`) et en exécutable
(`python circonscrit.py -h`);
- des exemples de Programmation Orientée Objet : classe `Point` et la
classe héritière `Vector`;
- un exemple d'utilisation du module `argparse` de la bibliothèque
standard, permettant la gestion des arguments de la ligne de
commande ;
- l'utilisation de tests unitaires sous la forme de `doctest` (tests
inclus dans les *docstrings* des éléments à tester).
Pour exécuter les tests unitaires du module :
- avec doctest: `python -m doctest -v circonscrit.py`
- avec pytests: `py.test --doctest-modules -v circonscrit.py`
- avec nose: `nosetests --with-doctest -v circonscrit.py`
"""
__author__
=
"Yannick Copin <y.copin@ipnl.in2p3.fr>"
__version__
=
"Time-stamp: <2014-01-12 22:19 ycopin@lyonovae03.in2p3.fr>"
# Définition d'une classe ==============================
class
Point
(
object): # *object* est la classe dont dérivent toutes les autres
"""
Classe définissant un `Point` du plan, caractérisé par ses
coordonnées `x`,`y`.
"""
def
__init__
(
self, x, y):
"""
Méthode d'instanciation à partir de deux coordonnées réelles.
>>>
Point
(
0
,1
) # doctest: +ELLIPSIS
<circonscrit.Point object at 0x...>
>>>
Point
(
1
+
3
j)
Traceback (most recent call last):
...
TypeError: __init__() takes exactly 3 arguments (2 given)
"""
try
: # Convertit les coords en `float`
self.x =
float(
x)
self.y =
float(
y)
except
(
ValueError
, TypeError
):
raise
TypeError
(
"Invalid input coordinates ({},{})"
.format
(
x, y))
def
__str__
(
self):
"""
Surcharge de la fonction `str()`: l'affichage *informel* de l'objet
dans l'interpréteur, p.ex. `print self` sera résolu comme
`self.__str__()`
Retourne une chaîne de caractères.
>>>
print
Point
(
1
,2
)
Point (x=1.0, y=2.0)
"""
return
"Point (x={p.x}, y={p.y})"
.format
(
p=
self)
def
isOrigin
(
self):
"""
Teste si le point est à l'origine en testant la nullité des deux
coordonnées.
Attention aux éventuelles erreurs d'arrondis: il faut tester
la nullité à la précision numérique près.
>>>
Point
(
1
,2
).isOrigin
(
)
False
>>>
Point
(
0
,0
).isOrigin
(
)
True
"""
import
sys
eps =
sys.float_info.epsilon # Le plus petit float non nul
return
((
abs(
self.x) <=
eps) and
(
abs(
self.y) <=
eps))
def
distance
(
self, other):
"""
Méthode de calcul de la distance du point (`self`) à un autre point
(`other`).
>>>
A =
Point
(
1
,0
); B =
Point
(
1
,1
); A.distance
(
B)
1.0
"""
from
math import
hypot
return
hypot
(
self.x -
other.x, self.y -
other.y) # sqrt(dx**2 + dy**2)
# Définition du point origine O
O =
Point
(
0
, 0
)
# Héritage de classe ==============================
class
Vector
(
Point):
"""
Un `Vector` hérite de `Point` avec des méthodes additionnelles
(p.ex. la négation d'un vecteur, l'addition de deux vecteurs, ou
la rotation d'un vecteur).
"""
def
__init__
(
self, A, B):
"""
Définit le vecteur `AB` à partir des deux points `A` et `B`.
>>>
Vector
(
Point
(
1
,0
), Point
(
1
,1
)) # doctest: +ELLIPSIS
<circonscrit.Vector object at 0x...>
>>>
Vector
(
0
, 1
)
Traceback (most recent call last):
...
AttributeError: 'int' object has no attribute 'x'
"""
# Initialisation de la classe parente
Point.__init__
(
self, B.x -
A.x, B.y -
A.y)
# Attribut propre à la classe dérivée
self.sqnorm =
self.x **
2
+
self.y **
2
# Norme du vecteur au carré
def
__str__
(
self):
"""
Surcharge de la fonction `str()`: `print self` sera résolu comme
`Vector.__str__(self)` (et non pas comme
`Point.__str__(self)`)
>>>
A =
Point
(
1
,0
); B =
Point
(
1
,1
); print
Vector
(
A,B)
Vector (x=0.0, y=1.0)
"""
return
"Vector (x={v.x}, y={v.y})"
.format
(
v=
self)
def
__add__
(
self, other):
"""
Surcharge de l'opérateur binaire `{self} + {other}`: l'instruction
sera résolue comme `self.__add__(other)`.
On construit une nouvelle instance de `Vector` à partir des
coordonnées propres à l'objet `self` et à l'autre opérande
`other`.
>>>
A =
Point
(
1
,0
); B =
Point
(
1
,1
)
>>>
print
Vector
(
A,B) +
Vector
(
B,O) # = Vector(A,O)
Vector (x=-1.0, y=0.0)
"""
return
Vector
(
O, Point
(
self.x +
other.x, self.y +
other.y))
def
__sub__
(
self, other):
"""
Surcharge de l'opérateur binaire `{self} - {other}`: l'instruction
sera résolue comme `self.__sub__(other)`.
Attention: ne surcharge pas l'opérateur unaire `-{self}`, géré
par `__neg__`.
>>>
A =
Point
(
1
,0
); B =
Point
(
1
,1
)
>>>
print
Vector
(
A,B) -
Vector
(
A,B) # Différence
Vector (x=0.0, y=0.0)
>>>
-
Vector
(
A,B) # Négation
Traceback (most recent call last):
...
TypeError: bad operand type for unary -: 'Vector'
"""
return
Vector
(
O, Point
(
self.x -
other.x, self.y -
other.y))
def
__eq__
(
self, other):
"""
Surcharge du test d'égalité `{self}=={other}`: l'instruction sera
résolue comme `self.__eq__(other)`.
>>>
Vector
(
O,Point
(
0
,1
)) ==
Vector
(
Point
(
1
,0
),Point
(
1
,1
))
True
"""
# On teste ici la nullité de la différence des 2
# vecteurs. D'autres tests auraient été possibles -- égalité
# des coordonnées, nullité de la norme de la différence,
# etc. -- mais on tire profit de la méthode héritée
# `Point.isOrigin()` testant la nullité des coordonnées (à la
# précision numérique près).
return
(
self -
other).isOrigin
(
)
def
__abs__
(
self):
"""
Surcharge la fonction `abs()` pour retourner la norme du vecteur.
>>>
abs(
Vector
(
Point
(
1
,0
), Point
(
1
,1
)))
1.0
"""
# On pourrait utiliser sqrt(self.sqnorm), mais c'est pour
# illustrer l'utilisation de la méthode héritée
# `Point.distance`...
return
Point.distance
(
self, O)
def
rotate
(
self, angle, deg=
False
):
"""
Rotation (dans le sens trigonométrique) du vecteur par un `angle`,
exprimé en radians ou en degrés.
>>>
Vector
(
Point
(
1
,0
),Point
(
1
,1
)).rotate
(
90
,deg=
True
) ==
Vector
(
O,Point
(-
1
,0
))
True
"""
from
cmath import
rect # Bibliothèque de fonctions complexes
# On calcule la rotation en passant dans le plan complexe
z =
complex(
self.x, self.y)
phase =
angle if
not
deg else
angle /
57.29577951308232
# [rad]
u =
rect
(
1.
, phase) # exp(i*phase)
zu =
z *
u # Rotation complexe
return
Vector
(
O, Point
(
zu.real, zu.imag))
def
circumscribedCircle
(
M, N, P):
"""
Calcule le centre et le rayon du cercle circonscrit aux points
M,N,P.
Retourne: (centre [Point],rayon [float])
Lève une exception `ValueError` si le rayon ou le centre du cercle
circonscrit n'est pas défini.
>>>
M =
Point
(-
1
,0
); N =
Point
(
1
,0
); P =
Point
(
0
,1
)
>>>
C,r =
circumscribedCircle
(
M,N,P) # Centre O, rayon 1
>>>
print
C.distance
(
O), r
0.0 1.0
>>>
circumscribedCircle
(
M,O,N) # Indéfini
Traceback (most recent call last):
...
ValueError: Undefined circumscribed circle radius.
"""
from
math import
sqrt
MN =
Vector
(
M, N)
NP =
Vector
(
N, P)
PM =
Vector
(
P, M)
# Rayon du cercle circonscrit
m =
abs(
NP) # |NP|
n =
abs(
PM) # |PM|
p =
abs(
MN) # |MN|
d =
(
m +
n +
p) *
(-
m +
n +
p) *
(
m -
n +
p) *
(
m +
n -
p)
if
d >
0
:
rad =
m *
n *
p /
sqrt
(
d)
else
:
raise
ValueError
(
"Undefined circumscribed circle radius."
)
# Centre du cercle circonscrit
d =
-
2
*
(
M.x *
NP.y +
N.x *
PM.y +
P.x *
MN.y)
if
d ==
0
:
raise
ValueError
(
"Undefined circumscribed circle center."
)
om2 =
Vector
(
O, M).sqnorm # |OM|**2
on2 =
Vector
(
O, N).sqnorm # |ON|**2
op2 =
Vector
(
O, P).sqnorm # |OP|**2
x0 =
-(
om2 *
NP.y +
on2 *
PM.y +
op2 *
MN.y) /
d
y0 =
(
om2 *
NP.x +
on2 *
PM.x +
op2 *
MN.x) /
d
return
(
Point
(
x0, y0), rad) # (centre [Point], R [float])
if
__name__
==
'__main__'
:
# start-argparse
import
argparse
parser =
argparse.ArgumentParser
(
usage=
"
%(prog)s
[-p/--plot] [-i/--input coordfile | x1,y1 x2,y2 x3,y3]"
,
description=
__doc__
)
parser.add_argument
(
'coords'
, nargs=
'*'
, type=
str, metavar=
'x,y'
,
help=
"Coordinates of point"
)
parser.add_argument
(
'-i'
, '--input'
, nargs=
'?'
, type=
file,
help=
"Coordinate file (one 'x,y' per line)"
)
parser.add_argument
(
'-p'
, '--plot'
, action=
"store_true"
, default=
False
,
help=
"Draw the circumscribed circle"
)
parser.add_argument
(
'--version'
, action=
'version'
, version=
__version__
)
args =
parser.parse_args
(
)
# end-argparse
if
args.input: # Lecture des coordonnées du fichier d'entrée
# Le fichier a déjà été ouvert en lecture par argparse (type=file)
args.coords =
[coords for
coords in
args.input
if
not
coords.strip
(
).startswith
(
'#'
)]
if
len(
args.coords) !=
3
: # Vérifie le nb de points
parser.error
(
"Specify 3 points by their coordinates 'x,y' (got {})"
.format
(
len(
args.coords)))
points =
[] # Liste des points
for
i, arg in
enumerate(
args.coords, start=
1
):
try
: # Déchiffrage de l'argument 'x,y'
x, y =
(
float(
t) for
t in
arg.split
(
','
))
except
ValueError
:
parser.error
(
"Cannot decipher coordinates #{}: '{}'"
.format
(
i, arg))
points.append
(
Point
(
x, y)) # Création du point et ajout à la liste
print
"#{:d}: {}"
.format
(
i, points[-
1
]) # Affichage du dernier point
# Calcul du cercle cisconscrit (lève une ValueError en cas de problème)
center, radius =
circumscribedCircle
(*
points) # Délistage
print
"Circumscribed circle: {}, radius: {}"
.format
(
center, radius)
if
args.plot: # Figure
import
matplotlib.pyplot as
P
fig =
P.figure
(
)
ax =
fig.add_subplot
(
1
, 1
, 1
, aspect=
'equal'
)
# Points
ax.plot
(
[p.x for
p in
points], [p.y for
p in
points], 'ko'
)
for
i, p in
enumerate(
points, start=
1
):
ax.annotate
(
"#{}"
.format
(
i), (
p.x, p.y),
xytext=(
5
, 5
), textcoords=
'offset points'
)
# Cercle circonscrit
c =
P.matplotlib.patches.Circle
((
center.x, center.y), radius=
radius,
fc=
'none'
, ec=
'k'
)
ax.add_patch
(
c)
ax.plot
(
center.x, center.y, 'r+'
)
P.show
(
)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
$ ./
circonscrit.py -
h
usage: circonscrit.py [-
p/--
plot] [-
i/--
input coordfile |
x1,y1 x2,y2 x3,y3]
positional arguments:
x,y Coordinates of point
optional arguments:
-
h, --
help show this help message and
exit
-
i [INPUT], --
input [INPUT]
Coordinate file (
one 'x,y'
per line)
-
p, --
plot Draw the circumscribed circle
--
version show program's version number and exit
$ ./circonscrit.py -p 0,0 1,0 1,1
Source : circonscrit.py
VIII-D. Matplotlib▲
VIII-D-1. Figure (relativement) simple▲
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Time-stamp: <2014-12-15 16:06:44 ycopin>
"""
Exemple un peu plus complexe de figure, incluant 2 axes, légendes, axes, etc.
"""
import
numpy as
N
import
matplotlib.pyplot as
P
try
:
import
seaborn # Amélioration de la charte graphique
except
ImportError
:
print
u"Seaborn n'est pas accessible, charte graphique par défaut."
x =
N.linspace
(-
N.pi, 3
*
N.pi, 2
*
360
)
# Signal carré
y =
N.sign
(
N.sin
(
x)) # = ± 1
# 3 premiers termes de la décomposition en série de Fourier
y1 =
4
/
N.pi *
N.sin
(
x) # Fondamentale
y2 =
4
/
N.pi *
N.sin
(
3
*
x) /
3
# 1re harmonique
y3 =
4
/
N.pi *
N.sin
(
5
*
x) /
5
# 2de harmonique
# Somme des 3 premières composantes
ytot =
y1 +
y2 +
y3
# Figure
fig =
P.figure
(
) # Création de la Figure
# 1er axe: composantes
ax1 =
fig.add_subplot
(
2
, 1
, 1
, # 1er axe d'une série de 2 × 1
ylabel=
"Composantes"
,
title=
u"Décomposition en série de Fourier"
)
ax1.plot
(
x, y1, label=
"Fondamental"
)
ax1.plot
(
x, y2, label=
u"1re harmonique"
)
ax1.plot
(
x, y3, label=
u"2de harmonique"
)
ax1.legend
(
loc=
"upper left"
, fontsize=
"x-small"
)
# 2nd axe: décomposition
ax2 =
fig.add_subplot
(
2
, 1
, 2
, # 2d axe d'une série de 2 × 1
ylabel=
u"Décomposition"
,
xlabel=
"x [rad]"
)
ax2.plot
(
x, y, lw=
2
, color=
'k'
, label=
u"Signal carré"
)
ax2.plot
(
x, ytot, lw=
2
, ls=
':'
, color=
'k'
, label=
u"Somme des composantes"
)
ax2.legend
(
loc=
"upper left"
, fontsize=
"x-small"
)
# Sauvegarde de la figure (pas d'affichage intéractif)
fig.savefig
(
"figure.png"
)
Source : figure.py
VIII-D-2. Filtres du 2d ordre▲
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import
numpy as
N
import
matplotlib.pyplot as
P
def
passeBas
(
x, Q=
1
):
"""
Filtre passe-bas en pulsation réduite *x* = omega/omega0, facteur de
qualité *Q*.
"""
return
1
/
(
1
-
x **
2
+
x /
Q *
1
j)
def
passeHaut
(
x, Q=
1
):
return
-
x **
2
/
(
1
-
x **
2
+
x /
Q *
1
j)
def
passeBande
(
x, Q=
1
):
return
1
/
(
1
+
Q *
(
x -
1
/
x) *
1
j)
def
coupeBande
(
x, Q=
1
):
return
(
1
-
x **
2
) /
(
1
-
x **
2
+
x /
Q *
1
j)
def
gainNphase
(
f, dB=
True
):
"""
Retourne le gain (éventuellement en dB) et la phase [rad] d'un
filtre de fonction de transfert complexe *f*.
"""
g =
N.abs(
f) # Gain
if
dB: # [dB]
g =
20
*
N.log10
(
g)
p =
N.angle
(
f) # [rad]
return
g, p
def
asympGain
(
x, pentes=(
0
, -
40
)):
lx =
N.log10
(
x)
return
N.where
(
lx <
0
, pentes[0
] *
lx, pentes[1
] *
lx)
def
asympPhase
(
x, phases=(
0
, -
N.pi)):
return
N.where
(
x <
1
, phases[0
], phases[1
])
def
diagBode
(
x, filtres, labels,
title=
''
, plim=
None
, gAsymp=
None
, pAsymp=
None
):
"""
Trace le diagramme de Bode -- gain [dB] et phase [rad] -- des filtres
de fonction de transfert complexe *filtres* en fonction de la pulsation
réduite *x*.
"""
fig =
P.figure
(
)
axg =
fig.add_subplot
(
2
, 1
, 1
, # Axe des gains
xscale=
'log'
,
ylabel=
'Gain [dB]'
)
axp =
fig.add_subplot
(
2
, 1
, 2
, # Axe des phases
sharex=
axg,
xlabel=
r'x = $\omega$/$\omega_0$'
, xscale=
'log'
,
ylabel=
'Phase [rad]'
)
lstyles =
['--'
, '-'
, '-.'
, ':'
]
for
f, label, ls in
zip(
filtres, labels, lstyles): # Tracé des courbes
g, p =
gainNphase
(
f, dB=
True
) # Calcul du gain et de la phase
axg.plot
(
x, g, lw=
2
, ls=
ls, label=
"Q="
+
str(
label)) # Gain
axp.plot
(
x, p, lw=
2
, ls=
ls) # Phase
# Asymptotes
if
gAsymp is
not
None
: # Gain
axg.plot
(
x, asympGain
(
x, gAsymp), 'k:'
, lw=
2
, label=
'_'
)
if
pAsymp is
not
None
: # Phase
#axp.plot(x, asympPhase(x,pAsymp), 'k:')
pass
axg.legend
(
loc=
'best'
, prop=
dict(
size=
'small'
))
# Labels des phases
axp.set_yticks
(
N.arange
(-
2
, 2.1
) *
N.pi /
2
)
axp.set_yticks
(
N.arange
(-
4
, 4.1
) *
N.pi /
4
, minor=
True
)
axp.set_yticklabels
(
[r'$-\pi$'
, r'$-\pi/2$'
, r'$0$'
, r'$\pi/2$'
, r'$\pi$'
])
# Domaine des phases
if
plim is
not
None
:
axp.set_ylim
(
plim)
# Ajouter les grilles
for
ax in
(
axg, axp):
ax.grid
(
) # x et y, majors
ax.grid
(
which=
'minor'
) # x et y, minors
# Ajustements fins
gmin, gmax =
axg.get_ylim
(
)
axg.set_ylim
(
gmin, max(
gmax, 3
))
fig.subplots_adjust
(
hspace=
0.1
)
axg.xaxis.set_major_formatter
(
P.matplotlib.ticker.ScalarFormatter
(
))
P.setp
(
axg.get_xticklabels
(
), visible=
False
)
if
title:
axg.set_title
(
title)
return
fig
if
__name__
==
'__main__'
:
#P.rc('mathtext', fontset='stixsans')
x =
N.logspace
(-
1
, 1
, 1000
) # de 0.1 à 10 en 1000 pas
# Facteurs de qualité
qs =
[0.25
, 1
/
N.sqrt
(
2
), 5
] # Valeurs numériques
labels =
[0.25
, r'$1/\sqrt{2}$'
, 5
] # Labels
# Calcul des fonctions de transfert complexes
pbs =
[ passeBas
(
x, Q=
q) for
q in
qs ]
phs =
[ passeHaut
(
x, Q=
q) for
q in
qs ]
pcs =
[ passeBande
(
x, Q=
q) for
q in
qs ]
cbs =
[ coupeBande
(
x, Q=
q) for
q in
qs ]
# Création des 4 diagrammes de Bode
figPB =
diagBode
(
x, pbs, labels, title=
'Filtre passe-bas'
,
plim=(-
N.pi, 0
),
gAsymp=(
0
, -
40
), pAsymp=(
0
, -
N.pi))
figPH =
diagBode
(
x, phs, labels, title=
'Filtre passe-haut'
,
plim=(
0
, N.pi),
gAsymp=(
40
, 0
), pAsymp=(
N.pi, 0
))
figPC =
diagBode
(
x, pcs, labels, title=
'Filtre passe-bande'
,
plim=(-
N.pi /
2
, N.pi /
2
),
gAsymp=(
20
, -
20
), pAsymp=(
N.pi /
2
, -
N.pi /
2
))
figCB =
diagBode
(
x, cbs, labels, title=
'Filtre coupe-bande'
,
plim=(-
N.pi /
2
, N.pi /
2
),
gAsymp=(
0
, 0
), pAsymp=(
0
, 0
))
P.show
(
)
Source : filtres2ndOrdre.py