Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/plot/misc.py
8815 views
1
#*****************************************************************************
2
# Distributed under the terms of the GNU General Public License (GPL)
3
#
4
# This code is distributed in the hope that it will be useful,
5
# but WITHOUT ANY WARRANTY; without even the implied warranty of
6
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
7
# General Public License for more details.
8
#
9
# The full text of the GPL is available at:
10
#
11
# http://www.gnu.org/licenses/
12
#*****************************************************************************
13
14
from functools import wraps
15
16
from sage.ext.fast_eval import fast_float, fast_float_constant, is_fast_float
17
18
from sage.structure.element import is_Vector
19
20
def setup_for_eval_on_grid(funcs, ranges, plot_points=None, return_vars=False):
21
"""
22
Calculate the necessary parameters to construct a list of points,
23
and make the functions fast_callable.
24
25
INPUT:
26
27
- ``funcs`` - a function, or a list, tuple, or vector of functions
28
29
- ``ranges`` - a list of ranges. A range can be a 2-tuple of
30
numbers specifying the minimum and maximum, or a 3-tuple giving
31
the variable explicitly.
32
33
- ``plot_points`` - a tuple of integers specifying the number of
34
plot points for each range. If a single number is specified, it
35
will be the value for all ranges. This defaults to 2.
36
37
- ``return_vars`` - (default False) If True, return the variables,
38
in order.
39
40
41
OUTPUT:
42
43
44
- ``fast_funcs`` - if only one function passed, then a fast
45
callable function. If funcs is a list or tuple, then a tuple
46
of fast callable functions is returned.
47
48
- ``range_specs`` - a list of range_specs: for each range, a
49
tuple is returned of the form (range_min, range_max,
50
range_step) such that ``srange(range_min, range_max,
51
range_step, include_endpoint=True)`` gives the correct points
52
for evaluation.
53
54
EXAMPLES::
55
56
sage: x,y,z=var('x,y,z')
57
sage: f(x,y)=x+y-z
58
sage: g(x,y)=x+y
59
sage: h(y)=-y
60
sage: sage.plot.misc.setup_for_eval_on_grid(f, [(0, 2),(1,3),(-4,1)], plot_points=5)
61
(<sage.ext...>, [(0.0, 2.0, 0.5), (1.0, 3.0, 0.5), (-4.0, 1.0, 1.25)])
62
sage: sage.plot.misc.setup_for_eval_on_grid([g,h], [(0, 2),(-1,1)], plot_points=5)
63
((<sage.ext...>, <sage.ext...>), [(0.0, 2.0, 0.5), (-1.0, 1.0, 0.5)])
64
sage: sage.plot.misc.setup_for_eval_on_grid([sin,cos], [(-1,1)], plot_points=9)
65
((<sage.ext...>, <sage.ext...>), [(-1.0, 1.0, 0.25)])
66
sage: sage.plot.misc.setup_for_eval_on_grid([lambda x: x^2,cos], [(-1,1)], plot_points=9)
67
((<function <lambda> ...>, <sage.ext...>), [(-1.0, 1.0, 0.25)])
68
sage: sage.plot.misc.setup_for_eval_on_grid([x+y], [(x,-1,1),(y,-2,2)])
69
((<sage.ext...>,), [(-1.0, 1.0, 2.0), (-2.0, 2.0, 4.0)])
70
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(x,-1,1),(y,-1,1)], plot_points=[4,9])
71
(<sage.ext...>, [(-1.0, 1.0, 0.6666666666666666), (-1.0, 1.0, 0.25)])
72
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(x,-1,1),(y,-1,1)], plot_points=[4,9,10])
73
Traceback (most recent call last):
74
...
75
ValueError: plot_points must be either an integer or a list of integers, one for each range
76
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(1,-1),(y,-1,1)], plot_points=[4,9,10])
77
Traceback (most recent call last):
78
...
79
ValueError: Some variable ranges specify variables while others do not
80
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(1,-1),(-1,1)], plot_points=5)
81
doctest:...: DeprecationWarning:
82
Unnamed ranges for more than one variable is deprecated and
83
will be removed from a future release of Sage; you can used
84
named ranges instead, like (x,0,2)
85
See http://trac.sagemath.org/7008 for details.
86
(<sage.ext...>, [(1.0, -1.0, 0.5), (-1.0, 1.0, 0.5)])
87
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(y,1,-1),(x,-1,1)], plot_points=5)
88
(<sage.ext...>, [(1.0, -1.0, 0.5), (-1.0, 1.0, 0.5)])
89
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(x,1,-1),(x,-1,1)], plot_points=5)
90
Traceback (most recent call last):
91
...
92
ValueError: range variables should be distinct, but there are duplicates
93
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(x,1,1),(y,-1,1)])
94
Traceback (most recent call last):
95
...
96
ValueError: plot start point and end point must be different
97
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(x,1,-1),(y,-1,1)], return_vars=True)
98
(<sage.ext...>, [(1.0, -1.0, 2.0), (-1.0, 1.0, 2.0)], [x, y])
99
sage: sage.plot.misc.setup_for_eval_on_grid(x+y, [(y,1,-1),(x,-1,1)], return_vars=True)
100
(<sage.ext...>, [(1.0, -1.0, 2.0), (-1.0, 1.0, 2.0)], [y, x])
101
"""
102
if max(map(len, ranges)) != min(map(len, ranges)):
103
raise ValueError, "Some variable ranges specify variables while others do not"
104
105
if len(ranges[0])==3:
106
vars = [r[0] for r in ranges]
107
ranges = [r[1:] for r in ranges]
108
if len(set(vars))<len(vars):
109
raise ValueError, "range variables should be distinct, but there are duplicates"
110
else:
111
vars, free_vars = unify_arguments(funcs)
112
if len(free_vars)>1:
113
from sage.misc.superseded import deprecation
114
deprecation(7008, "Unnamed ranges for more than one variable is deprecated and will be removed from a future release of Sage; you can used named ranges instead, like (x,0,2)")
115
116
# pad the variables if we don't have enough
117
nargs = len(ranges)
118
if len(vars)<nargs:
119
vars += ('_',)*(nargs-len(vars))
120
121
ranges = [[float(z) for z in r] for r in ranges]
122
123
if plot_points is None:
124
plot_points=2
125
126
if not isinstance(plot_points, (list, tuple)):
127
plot_points = [plot_points]*len(ranges)
128
elif len(plot_points)!=nargs:
129
raise ValueError, "plot_points must be either an integer or a list of integers, one for each range"
130
131
plot_points = [int(p) if p>=2 else 2 for p in plot_points]
132
range_steps = [abs(range[1] - range[0])/(p-1) for range, p in zip(ranges, plot_points)]
133
if min(range_steps) == float(0):
134
raise ValueError, "plot start point and end point must be different"
135
136
options={}
137
if nargs==1:
138
options['expect_one_var']=True
139
140
if is_Vector(funcs):
141
funcs = list(funcs)
142
143
#TODO: raise an error if there is a function/method in funcs that takes more values than we have ranges
144
145
if return_vars:
146
return fast_float(funcs, *vars,**options), [tuple(range+[range_step]) for range,range_step in zip(ranges, range_steps)], vars
147
else:
148
return fast_float(funcs, *vars,**options), [tuple(range+[range_step]) for range,range_step in zip(ranges, range_steps)]
149
150
151
def unify_arguments(funcs):
152
"""
153
Returns a tuple of variables of the functions, as well as the
154
number of "free" variables (i.e., variables that defined in a
155
callable function).
156
157
INPUT:
158
159
- ``funcs`` -- a list of functions; these can be symbolic
160
expressions, polynomials, etc
161
162
OUTPUT: functions, expected arguments
163
164
- A tuple of variables in the functions
165
166
- A tuple of variables that were "free" in the functions
167
168
EXAMPLES:
169
170
sage: x,y,z=var('x,y,z')
171
sage: f(x,y)=x+y-z
172
sage: g(x,y)=x+y
173
sage: h(y)=-y
174
sage: sage.plot.misc.unify_arguments((f,g,h))
175
((x, y, z), (z,))
176
sage: sage.plot.misc.unify_arguments((g,h))
177
((x, y), ())
178
sage: sage.plot.misc.unify_arguments((f,z))
179
((x, y, z), (z,))
180
sage: sage.plot.misc.unify_arguments((h,z))
181
((y, z), (z,))
182
sage: sage.plot.misc.unify_arguments((x+y,x-y))
183
((x, y), (x, y))
184
"""
185
from sage.symbolic.callable import is_CallableSymbolicExpression
186
187
vars=set()
188
free_variables=set()
189
if not isinstance(funcs, (list, tuple)):
190
funcs=[funcs]
191
192
for f in funcs:
193
if is_CallableSymbolicExpression(f):
194
f_args=set(f.arguments())
195
vars.update(f_args)
196
else:
197
f_args=set()
198
199
try:
200
free_vars = set(f.variables()).difference(f_args)
201
vars.update(free_vars)
202
free_variables.update(free_vars)
203
except AttributeError:
204
# we probably have a constant
205
pass
206
return tuple(sorted(vars, key=lambda x: str(x))), tuple(sorted(free_variables, key=lambda x: str(x)))
207
208
#For backward compatibility -- see #9907.
209
from sage.misc.decorators import options, suboptions, rename_keyword
210
211
def _multiple_of_constant(n,pos,const):
212
"""
213
Function for internal use in formatting ticks on axes with
214
nice-looking multiples of various symbolic constants, such
215
as `\pi` or `e`. Should only be used via keyword argument
216
`tick_formatter` in :meth:`plot.show`. See documentation
217
for the matplotlib.ticker module for more details.
218
219
EXAMPLES:
220
221
Here is the intended use::
222
223
sage: plot(sin(x), (x,0,2*pi), ticks=pi/3, tick_formatter=pi)
224
225
Here is an unintended use, which yields unexpected (and probably
226
undesired) results::
227
228
sage: plot(x^2, (x, -2, 2), tick_formatter=pi)
229
230
We can also use more unusual constant choices::
231
232
sage: plot(ln(x), (x,0,10), ticks=e, tick_formatter=e)
233
sage: plot(x^2, (x,0,10), ticks=[sqrt(2),8], tick_formatter=sqrt(2))
234
"""
235
from sage.misc.latex import latex
236
from sage.rings.arith import convergents
237
c=[i for i in convergents(n/const.n()) if i.denominator()<12]
238
return '$%s$'%latex(c[-1]*const)
239
240
241
def get_matplotlib_linestyle(linestyle, return_type):
242
"""
243
Function which translates between matplotlib linestyle in short notation
244
(i.e. '-', '--', ':', '-.') and long notation (i.e. 'solid', 'dashed',
245
'dotted', 'dashdot' ). If linestyle is none of these allowed options the
246
function raises a ValueError.
247
248
INPUT:
249
250
- ``linestyle`` - The style of the line, which is one of
251
- ``"-"`` or ``"solid"``
252
- ``"--"`` or ``"dashed"``
253
- ``"-."`` or ``"dash dot"``
254
- ``":"`` or ``"dotted"``
255
- ``"None"`` or ``" "`` or ``""`` (nothing)
256
257
The linestyle can also be prefixed with a drawing style (e.g., ``"steps--"``)
258
259
- ``"default"`` (connect the points with straight lines)
260
- ``"steps"`` or ``"steps-pre"`` (step function; horizontal
261
line is to the left of point)
262
- ``"steps-mid"`` (step function; points are in the middle of
263
horizontal lines)
264
- ``"steps-post"`` (step function; horizontal line is to the
265
right of point)
266
267
If ``linestyle`` is ``None`` (of type NoneType), then we return it
268
back unmodified.
269
270
- ``return_type`` - The type of linestyle that should be output. This
271
argument takes only two values - ``"long"`` or ``"short"``.
272
273
EXAMPLES:
274
275
Here is an example how to call this function::
276
277
sage: from sage.plot.misc import get_matplotlib_linestyle
278
sage: get_matplotlib_linestyle(':', return_type='short')
279
':'
280
281
sage: get_matplotlib_linestyle(':', return_type='long')
282
'dotted'
283
284
TESTS:
285
286
Make sure that if the input is already in the desired format, then it
287
is unchanged::
288
289
sage: get_matplotlib_linestyle(':', 'short')
290
':'
291
292
Empty linestyles should be handled properly::
293
294
sage: get_matplotlib_linestyle("", 'short')
295
''
296
sage: get_matplotlib_linestyle("", 'long')
297
'None'
298
sage: get_matplotlib_linestyle(None, 'short') is None
299
True
300
301
Linestyles with ``"default"`` or ``"steps"`` in them should also be
302
properly handled. For instance, matplotlib understands only the short
303
version when ``"steps"`` is used::
304
305
sage: get_matplotlib_linestyle("default", "short")
306
''
307
sage: get_matplotlib_linestyle("steps--", "short")
308
'steps--'
309
sage: get_matplotlib_linestyle("steps-predashed", "long")
310
'steps-pre--'
311
312
Finally, raise error on invalid linestyles::
313
314
sage: get_matplotlib_linestyle("isthissage", "long")
315
Traceback (most recent call last):
316
...
317
ValueError: WARNING: Unrecognized linestyle 'isthissage'. Possible
318
linestyle options are:
319
{'solid', 'dashed', 'dotted', dashdot', 'None'}, respectively {'-',
320
'--', ':', '-.', ''}
321
322
"""
323
long_to_short_dict={'solid' : '-','dashed' : '--', 'dotted' : ':',
324
'dashdot':'-.'}
325
short_to_long_dict={'-' : 'solid','--' : 'dashed', ':' : 'dotted',
326
'-.':'dashdot'}
327
328
# We need this to take care of region plot. Essentially, if None is
329
# passed, then we just return back the same thing.
330
if linestyle is None:
331
return None
332
333
if linestyle.startswith("default"):
334
return get_matplotlib_linestyle(linestyle.strip("default"), "short")
335
elif linestyle.startswith("steps"):
336
if linestyle.startswith("steps-mid"):
337
return "steps-mid" + get_matplotlib_linestyle(
338
linestyle.strip("steps-mid"), "short")
339
elif linestyle.startswith("steps-post"):
340
return "steps-post" + get_matplotlib_linestyle(
341
linestyle.strip("steps-post"), "short")
342
elif linestyle.startswith("steps-pre"):
343
return "steps-pre" + get_matplotlib_linestyle(
344
linestyle.strip("steps-pre"), "short")
345
else:
346
return "steps" + get_matplotlib_linestyle(
347
linestyle.strip("steps"), "short")
348
349
if return_type == 'short':
350
if linestyle in short_to_long_dict.keys():
351
return linestyle
352
elif linestyle == "" or linestyle == " " or linestyle == "None":
353
return ''
354
elif linestyle in long_to_short_dict.keys():
355
return long_to_short_dict[linestyle]
356
else:
357
raise ValueError("WARNING: Unrecognized linestyle '%s'. "
358
"Possible linestyle options are:\n{'solid', "
359
"'dashed', 'dotted', dashdot', 'None'}, "
360
"respectively {'-', '--', ':', '-.', ''}"%
361
(linestyle))
362
363
elif return_type == 'long':
364
if linestyle in long_to_short_dict.keys():
365
return linestyle
366
elif linestyle == "" or linestyle == " " or linestyle == "None":
367
return "None"
368
elif linestyle in short_to_long_dict.keys():
369
return short_to_long_dict[linestyle]
370
else:
371
raise ValueError("WARNING: Unrecognized linestyle '%s'. "
372
"Possible linestyle options are:\n{'solid', "
373
"'dashed', 'dotted', dashdot', 'None'}, "
374
"respectively {'-', '--', ':', '-.', ''}"%
375
(linestyle))
376
377
378