Skip to content

Commit 6eaa314

Browse files
Copilotmmcky
andauthored
ENH: Update default timing precision to 4 digits and add global precision control for Timer and timeit (#805)
* Initial plan * Implement global timing precision control with 4-digit default - Create quantecon.timings module with float_precision() function - Update Timer class to use global precision as default (was 2, now 4) - Update timeit function to use global precision as default - Update tac/toc/loop_timer functions to use global precision as default - Add comprehensive tests for new global precision functionality - All 27 timing tests passing (19 existing + 8 new) - Backward compatibility maintained - explicit precision parameters still work Co-authored-by: mmcky <8263752+mmcky@users.noreply.github.com> * Revert tic/tac/toc/loop_timer to original defaults - keep only Timer and timeit using global precision Co-authored-by: mmcky <8263752+mmcky@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mmcky <8263752+mmcky@users.noreply.github.com>
1 parent 08a6188 commit 6eaa314

File tree

5 files changed

+185
-10
lines changed

5 files changed

+185
-10
lines changed

quantecon/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from . import quad
1919
from . import random
2020
from . import optimize
21+
from . import timings
2122

2223
#-Objects-#
2324
from ._compute_fp import compute_fixed_point

quantecon/timings/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""
2+
Timing precision configuration for QuantEcon.py
3+
"""
4+
5+
from .timings import float_precision, get_default_precision
6+
7+
__all__ = ['float_precision', 'get_default_precision']

quantecon/timings/timings.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Global timing precision configuration for QuantEcon.py
3+
4+
This module provides global control over the precision used in timing outputs
5+
across all timing functions in QuantEcon.
6+
"""
7+
8+
# Global variable to store the current float precision
9+
_DEFAULT_FLOAT_PRECISION = 4
10+
11+
12+
def float_precision(precision=None):
13+
"""
14+
Get or set the global float precision for timing outputs.
15+
16+
Parameters
17+
----------
18+
precision : int, optional
19+
Number of decimal places to display in timing outputs.
20+
If None, returns the current precision setting.
21+
22+
Returns
23+
-------
24+
int
25+
Current precision value if precision=None, otherwise None.
26+
27+
Examples
28+
--------
29+
Get current precision:
30+
>>> import quantecon as qe
31+
>>> current = qe.timings.float_precision()
32+
>>> print(f"Current precision: {current}")
33+
34+
Set new precision:
35+
>>> qe.timings.float_precision(6)
36+
>>> # All subsequent timing outputs will use 6 decimal places
37+
38+
Reset to default:
39+
>>> qe.timings.float_precision(4)
40+
"""
41+
global _DEFAULT_FLOAT_PRECISION
42+
43+
if precision is None:
44+
return _DEFAULT_FLOAT_PRECISION
45+
46+
if not isinstance(precision, int) or precision < 0:
47+
raise ValueError("precision must be a non-negative integer")
48+
49+
_DEFAULT_FLOAT_PRECISION = precision
50+
51+
52+
def get_default_precision():
53+
"""
54+
Get the current default precision setting.
55+
56+
Returns
57+
-------
58+
int
59+
Current default precision for timing outputs.
60+
"""
61+
return _DEFAULT_FLOAT_PRECISION

quantecon/util/tests/test_timing.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
from numpy.testing import assert_allclose, assert_
88
from quantecon.util import tic, tac, toc, loop_timer, Timer, timeit
9+
import quantecon as qe
910

1011

1112
class TestTicTacToc:
@@ -291,3 +292,106 @@ def test_func():
291292
assert 'elapsed' in result2
292293
assert len(result2['elapsed']) == 2
293294

295+
296+
class TestGlobalPrecision:
297+
"""Test the new global precision control functionality."""
298+
299+
def setup_method(self):
300+
"""Save original precision and restore after each test."""
301+
self.original_precision = qe.timings.float_precision()
302+
303+
def teardown_method(self):
304+
"""Restore original precision after each test."""
305+
qe.timings.float_precision(self.original_precision)
306+
307+
def test_default_precision_is_4(self):
308+
"""Test that default precision is now 4."""
309+
# Reset to ensure we test the true default
310+
qe.timings.float_precision(4)
311+
assert qe.timings.float_precision() == 4
312+
313+
def test_float_precision_get_set(self):
314+
"""Test getting and setting precision."""
315+
# Test setting various precisions
316+
for precision in [0, 1, 2, 3, 4, 5, 6, 10]:
317+
qe.timings.float_precision(precision)
318+
assert qe.timings.float_precision() == precision
319+
320+
def test_float_precision_validation(self):
321+
"""Test that float_precision validates input."""
322+
# Test invalid inputs
323+
try:
324+
qe.timings.float_precision(-1)
325+
assert False, "Should raise ValueError for negative precision"
326+
except ValueError as e:
327+
assert "non-negative integer" in str(e)
328+
329+
try:
330+
qe.timings.float_precision("4")
331+
assert False, "Should raise ValueError for string input"
332+
except ValueError as e:
333+
assert "non-negative integer" in str(e)
334+
335+
try:
336+
qe.timings.float_precision(4.5)
337+
assert False, "Should raise ValueError for float input"
338+
except ValueError as e:
339+
assert "non-negative integer" in str(e)
340+
341+
def test_timer_uses_global_precision(self):
342+
"""Test that Timer class uses global precision by default."""
343+
# Set global precision
344+
qe.timings.float_precision(6)
345+
346+
# Create timer without explicit precision
347+
timer = Timer(verbose=False)
348+
assert timer.precision == 6
349+
350+
# Test with different global precision
351+
qe.timings.float_precision(2)
352+
timer2 = Timer(verbose=False)
353+
assert timer2.precision == 2
354+
355+
def test_timer_explicit_precision_overrides_global(self):
356+
"""Test that explicit precision overrides global setting."""
357+
qe.timings.float_precision(6)
358+
359+
# Explicit precision should override global
360+
timer = Timer(precision=3, verbose=False)
361+
assert timer.precision == 3
362+
363+
def test_tac_toc_keep_original_defaults(self):
364+
"""Test that tac/toc functions maintain original default (digits=2)."""
365+
# These functions are deprecated and should maintain original behavior
366+
tic()
367+
time.sleep(0.01)
368+
369+
# These should use digits=2 by default, not global precision
370+
result_tac = tac(verbose=False) # Uses default digits=2
371+
result_toc = toc(verbose=False) # Uses default digits=2
372+
373+
# Just verify they work without error
374+
assert result_tac > 0
375+
assert result_toc > 0
376+
377+
def test_loop_timer_keeps_original_default(self):
378+
"""Test that loop_timer maintains original default (digits=2)."""
379+
def test_func():
380+
time.sleep(0.001)
381+
382+
# Should use digits=2 by default, not global precision
383+
result = loop_timer(2, test_func, verbose=False)
384+
assert len(result) == 2 # Returns (average_time, average_of_best)
385+
386+
def test_timeit_uses_global_precision(self):
387+
"""Test that timeit function uses global precision by default."""
388+
def test_func():
389+
time.sleep(0.001)
390+
391+
qe.timings.float_precision(6)
392+
393+
# Should use global precision without error
394+
result = timeit(test_func, runs=2, verbose=False, results=True)
395+
assert 'elapsed' in result
396+
assert len(result['elapsed']) == 2
397+

quantecon/util/timing.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
import time
66
import numpy as np
7+
from ..timings.timings import get_default_precision
78

89

910
class __Timer__:
@@ -190,8 +191,9 @@ class Timer:
190191
----------
191192
message : str, optional(default="")
192193
Custom message to display with timing results.
193-
precision : int, optional(default=2)
194-
Number of decimal places to display for seconds.
194+
precision : int, optional(default=None)
195+
Number of decimal places to display for seconds. If None, uses
196+
the global default precision from quantecon.timings.
195197
unit : str, optional(default="seconds")
196198
Unit to display timing in. Options: "seconds", "milliseconds", "microseconds"
197199
verbose : bool, optional(default=True)
@@ -208,13 +210,13 @@ class Timer:
208210
>>> with Timer():
209211
... # some code
210212
... pass
211-
0.00 seconds elapsed
213+
0.0000 seconds elapsed
212214
213215
With custom message and precision:
214-
>>> with Timer("Computing results", precision=4):
216+
>>> with Timer("Computing results", precision=6):
215217
... # some code
216218
... pass
217-
Computing results: 0.0001 seconds elapsed
219+
Computing results: 0.000001 seconds elapsed
218220
219221
Store elapsed time for comparison:
220222
>>> timer = Timer(verbose=False)
@@ -225,9 +227,9 @@ class Timer:
225227
Method took 0.000123 seconds
226228
"""
227229

228-
def __init__(self, message="", precision=2, unit="seconds", verbose=True):
230+
def __init__(self, message="", precision=None, unit="seconds", verbose=True):
229231
self.message = message
230-
self.precision = precision
232+
self.precision = precision if precision is not None else get_default_precision()
231233
self.unit = unit.lower()
232234
self.verbose = verbose
233235
self.elapsed = None
@@ -347,7 +349,7 @@ def timeit(func, runs=1, stats_only=False, verbose=True, results=False, **timer_
347349
# Extract Timer parameters
348350
timer_params = {
349351
'message': timer_kwargs.pop('message', ''),
350-
'precision': timer_kwargs.pop('precision', 2),
352+
'precision': timer_kwargs.pop('precision', None), # None will use global default
351353
'unit': timer_kwargs.pop('unit', 'seconds'),
352354
'verbose': timer_kwargs.pop('verbose', True) # Timer verbose parameter
353355
}
@@ -376,7 +378,7 @@ def timeit(func, runs=1, stats_only=False, verbose=True, results=False, **timer_
376378
if show_output and not stats_only:
377379
# Convert to requested unit for display
378380
unit = timer_params['unit'].lower()
379-
precision = timer_params['precision']
381+
precision = timer_params['precision'] if timer_params['precision'] is not None else get_default_precision()
380382

381383
if unit == "milliseconds":
382384
elapsed_display = timer.elapsed * 1000
@@ -399,7 +401,7 @@ def timeit(func, runs=1, stats_only=False, verbose=True, results=False, **timer_
399401
if show_output:
400402
# Convert to requested unit for display
401403
unit = timer_params['unit'].lower()
402-
precision = timer_params['precision']
404+
precision = timer_params['precision'] if timer_params['precision'] is not None else get_default_precision()
403405

404406
if unit == "milliseconds":
405407
avg_display = average * 1000

0 commit comments

Comments
 (0)