Show More
@@ -0,0 +1,92 b'' | |||||
|
1 | """ | |||
|
2 | Module containing custom doctests. | |||
|
3 | ||||
|
4 | """ | |||
|
5 | ||||
|
6 | def str_to_array(s): | |||
|
7 | """ | |||
|
8 | Simplistic converter of strings from repr to float NumPy arrays. | |||
|
9 | ||||
|
10 | If the repr representation has ellipsis in it, then this will fail. | |||
|
11 | ||||
|
12 | Parameters | |||
|
13 | ---------- | |||
|
14 | s : str | |||
|
15 | The repr version of a NumPy array. | |||
|
16 | ||||
|
17 | Examples | |||
|
18 | -------- | |||
|
19 | >>> s = "array([ 0.3, inf, nan])" | |||
|
20 | >>> a = str_to_array(s) | |||
|
21 | ||||
|
22 | """ | |||
|
23 | import numpy as np | |||
|
24 | ||||
|
25 | # Need to make sure eval() knows about inf and nan. | |||
|
26 | # This also assumes default printoptions for NumPy. | |||
|
27 | from numpy import inf, nan | |||
|
28 | ||||
|
29 | if s.startswith(u'array'): | |||
|
30 | # Remove array( and ) | |||
|
31 | s = s[6:-1] | |||
|
32 | ||||
|
33 | if s.startswith(u'['): | |||
|
34 | a = np.array(eval(s), dtype=float) | |||
|
35 | else: | |||
|
36 | # Assume its a regular float. Force 1D so we can index into it. | |||
|
37 | a = np.atleast_1d(float(s)) | |||
|
38 | return a | |||
|
39 | ||||
|
40 | def float_doctest(sphinx_shell, args, input_lines, found, submitted): | |||
|
41 | """ | |||
|
42 | Doctest which allow the submitted output to vary slightly from the input. | |||
|
43 | ||||
|
44 | Here is how it might appear in an rst file: | |||
|
45 | ||||
|
46 | .. code-block:: rst | |||
|
47 | ||||
|
48 | .. ipython:: | |||
|
49 | ||||
|
50 | @doctest float | |||
|
51 | In [1]: 0.1 + 0.2 | |||
|
52 | Out[1]: 0.3 | |||
|
53 | ||||
|
54 | """ | |||
|
55 | import numpy as np | |||
|
56 | ||||
|
57 | if len(args) == 2: | |||
|
58 | rtol = 1e-05 | |||
|
59 | atol = 1e-08 | |||
|
60 | else: | |||
|
61 | # Both must be specified if any are specified. | |||
|
62 | try: | |||
|
63 | rtol = float(args[2]) | |||
|
64 | atol = float(args[3]) | |||
|
65 | except IndexError: | |||
|
66 | e = ("Both `rtol` and `atol` must be specified " | |||
|
67 | "if either are specified: {0}".format(args)) | |||
|
68 | raise IndexError(e) | |||
|
69 | ||||
|
70 | try: | |||
|
71 | submitted = str_to_array(submitted) | |||
|
72 | found = str_to_array(found) | |||
|
73 | except: | |||
|
74 | # For example, if the array is huge and there are ellipsis in it. | |||
|
75 | error = True | |||
|
76 | else: | |||
|
77 | found_isnan = np.isnan(found) | |||
|
78 | submitted_isnan = np.isnan(submitted) | |||
|
79 | error = not np.allclose(found_isnan, submitted_isnan) | |||
|
80 | error |= not np.allclose(found[~found_isnan], | |||
|
81 | submitted[~submitted_isnan], | |||
|
82 | rtol=rtol, atol=atol) | |||
|
83 | ||||
|
84 | if error: | |||
|
85 | e = ('doctest float comparison failure for input_lines={0} with ' | |||
|
86 | 'found_output={1} and submitted ' | |||
|
87 | 'output="{2}"'.format(input_lines, repr(found), repr(submitted)) ) | |||
|
88 | raise RuntimeError(e) | |||
|
89 | ||||
|
90 | doctests = { | |||
|
91 | 'float': float_doctest, | |||
|
92 | } |
@@ -136,27 +136,6 b' COMMENT, INPUT, OUTPUT = range(3)' | |||||
136 | #----------------------------------------------------------------------------- |
|
136 | #----------------------------------------------------------------------------- | |
137 | # Functions and class declarations |
|
137 | # Functions and class declarations | |
138 | #----------------------------------------------------------------------------- |
|
138 | #----------------------------------------------------------------------------- | |
139 | def str_to_array(s): |
|
|||
140 | """ |
|
|||
141 | Simplistic converter of strings from repr to float NumPy arrays. |
|
|||
142 |
|
||||
143 | """ |
|
|||
144 | import numpy as np |
|
|||
145 |
|
||||
146 | # Handle infs (assumes default printoptions for NumPy) |
|
|||
147 | s = s.replace(u'inf', u'np.inf') |
|
|||
148 | s = s.replace(u'nan', u'np.nan') |
|
|||
149 |
|
||||
150 | if s.startswith(u'array'): |
|
|||
151 | # Remove array( and ) |
|
|||
152 | s = s[6:-1] |
|
|||
153 |
|
||||
154 | if s.startswith(u'['): |
|
|||
155 | a = np.array(eval(s), dtype=float) |
|
|||
156 | else: |
|
|||
157 | # Assume its a regular float. Force 1D so we can index into it. |
|
|||
158 | a = np.atleast_1d(float(s)) |
|
|||
159 | return a |
|
|||
160 |
|
139 | |||
161 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): |
|
140 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): | |
162 | """ |
|
141 | """ | |
@@ -252,6 +231,7 b' def block_parser(part, rgxin, rgxout, fmtin, fmtout):' | |||||
252 |
|
231 | |||
253 | return block |
|
232 | return block | |
254 |
|
233 | |||
|
234 | ||||
255 | class EmbeddedSphinxShell(object): |
|
235 | class EmbeddedSphinxShell(object): | |
256 | """An embedded IPython instance to run inside Sphinx""" |
|
236 | """An embedded IPython instance to run inside Sphinx""" | |
257 |
|
237 | |||
@@ -355,7 +335,6 b' class EmbeddedSphinxShell(object):' | |||||
355 | image_directive = '\n'.join(imagerows) |
|
335 | image_directive = '\n'.join(imagerows) | |
356 | return image_file, image_directive |
|
336 | return image_file, image_directive | |
357 |
|
337 | |||
358 |
|
||||
359 | # Callbacks for each type of token |
|
338 | # Callbacks for each type of token | |
360 | def process_input(self, data, input_prompt, lineno): |
|
339 | def process_input(self, data, input_prompt, lineno): | |
361 | """Process data block for INPUT token.""" |
|
340 | """Process data block for INPUT token.""" | |
@@ -452,8 +431,7 b' class EmbeddedSphinxShell(object):' | |||||
452 | 'output="{2}"'.format(input_lines, found, submitted) ) |
|
431 | 'output="{2}"'.format(input_lines, found, submitted) ) | |
453 | raise RuntimeError(e) |
|
432 | raise RuntimeError(e) | |
454 | else: |
|
433 | else: | |
455 |
self. |
|
434 | self.custom_doctest(decorator, input_lines, found, submitted) | |
456 | found, submitted) |
|
|||
457 |
|
435 | |||
458 | def process_comment(self, data): |
|
436 | def process_comment(self, data): | |
459 | """Process data fPblock for COMMENT token.""" |
|
437 | """Process data fPblock for COMMENT token.""" | |
@@ -594,56 +572,21 b' class EmbeddedSphinxShell(object):' | |||||
594 |
|
572 | |||
595 | return output |
|
573 | return output | |
596 |
|
574 | |||
597 |
def |
|
575 | def custom_doctest(self, decorator, input_lines, found, submitted): | |
598 | """ |
|
576 | """ | |
599 | Perform a specialized doctest. |
|
577 | Perform a specialized doctest. | |
600 |
|
578 | |||
601 | """ |
|
579 | """ | |
602 | # Requires NumPy |
|
580 | from .custom_doctests import doctests | |
603 | import numpy as np |
|
|||
604 |
|
||||
605 | valid_types = set(['float']) |
|
|||
606 |
|
581 | |||
607 | args = decorator.split() |
|
582 | args = decorator.split() | |
608 |
|
||||
609 | doctest_type = args[1] |
|
583 | doctest_type = args[1] | |
610 |
if doctest_type |
|
584 | if doctest_type in doctests: | |
|
585 | doctests[doctest_type](self, args, input_lines, found, submitted) | |||
|
586 | else: | |||
611 | e = "Invalid option to @doctest: {0}".format(doctest_type) |
|
587 | e = "Invalid option to @doctest: {0}".format(doctest_type) | |
612 | raise Exception(e) |
|
588 | raise Exception(e) | |
613 |
|
589 | |||
614 | if len(args) == 2: |
|
|||
615 | rtol = 1e-05 |
|
|||
616 | atol = 1e-08 |
|
|||
617 | else: |
|
|||
618 | # Both must be specified if any are specified. |
|
|||
619 | try: |
|
|||
620 | rtol = float(args[2]) |
|
|||
621 | atol = float(args[3]) |
|
|||
622 | except IndexError: |
|
|||
623 | e = ("Both `rtol` and `atol` must be specified " |
|
|||
624 | "if either are specified: {0}".format(args)) |
|
|||
625 | raise IndexError(e) |
|
|||
626 |
|
||||
627 | try: |
|
|||
628 | submitted = str_to_array(submitted) |
|
|||
629 | found = str_to_array(found) |
|
|||
630 | except: |
|
|||
631 | # For example, if the array is huge and there are ellipsis in it. |
|
|||
632 | error = True |
|
|||
633 | else: |
|
|||
634 | found_isnan = np.isnan(found) |
|
|||
635 | submitted_isnan = np.isnan(submitted) |
|
|||
636 | error = not np.allclose(found_isnan, submitted_isnan) |
|
|||
637 | error |= not np.allclose(found[~found_isnan], |
|
|||
638 | submitted[~submitted_isnan], |
|
|||
639 | rtol=rtol, atol=atol) |
|
|||
640 |
|
||||
641 | if error: |
|
|||
642 | e = ('doctest float comparison failure for input_lines="{0}" with ' |
|
|||
643 | 'found_output="{1}" and submitted ' |
|
|||
644 | 'output="{2}"'.format(input_lines, found, submitted) ) |
|
|||
645 | raise RuntimeError(e) |
|
|||
646 |
|
||||
647 |
|
590 | |||
648 | class IPythonDirective(Directive): |
|
591 | class IPythonDirective(Directive): | |
649 |
|
592 |
General Comments 0
You need to be logged in to leave comments.
Login now