Skip to content

Commit c569fc4

Browse files
authored
Merge branch 'main' into copilot/fix-790
2 parents 7fa425b + 6eaa314 commit c569fc4

File tree

10 files changed

+247
-14
lines changed

10 files changed

+247
-14
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
- name: Checkout source
7171
uses: actions/checkout@v5
7272
- name: Set up Python
73-
uses: actions/setup-python@v5
73+
uses: actions/setup-python@v6
7474
with:
7575
python-version: "3.x"
7676
- name: Install flit

environment_np2.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ dependencies:
99
- numpy>=2
1010
- scipy
1111
- pandas
12-
- numba>=0.61
12+
- numba=0.62.0rc2
1313
- sympy
1414
- ipython
1515
- flake8

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/game_theory/game_converters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from .normal_form_game import Player, NormalFormGame
3232

3333

34-
def str2num(s):
34+
def _str2num(s):
3535
"""
3636
Convert string to appropriate numeric type.
3737
@@ -145,7 +145,7 @@ def _parse(string):
145145

146146
N = int(tokens.pop(0))
147147
nums_actions = tuple(int(tokens.pop(0)) for _ in range(N))
148-
payoffs = np.array([str2num(s) for s in tokens])
148+
payoffs = np.array([_str2num(s) for s in tokens])
149149

150150
na = np.prod(nums_actions)
151151
payoffs2d = payoffs.reshape(N, na)

quantecon/game_theory/support_enumeration.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ def support_enumeration(g):
3535
list(tuple(ndarray(float, ndim=1)))
3636
List containing tuples of Nash equilibrium mixed actions.
3737
38+
Examples
39+
--------
40+
>>> from pprint import pprint
41+
>>> np.set_printoptions(precision=4) # Reduce the digits printed
42+
>>> bimatrix = [[(3, 3), (3, 2)],
43+
... [(2, 2), (5, 6)],
44+
... [(0, 3), (6, 1)]]
45+
>>> g = NormalFormGame(bimatrix)
46+
>>> NEs = support_enumeration(g)
47+
>>> pprint(NEs)
48+
[(array([1., 0., 0.]), array([1., 0.])),
49+
(array([0.8, 0.2, 0. ]), array([0.6667, 0.3333])),
50+
(array([0. , 0.3333, 0.6667]), array([0.3333, 0.6667]))]
51+
3852
"""
3953
return list(support_enumeration_gen(g))
4054

@@ -53,6 +67,21 @@ def support_enumeration_gen(g):
5367
tuple(ndarray(float, ndim=1))
5468
Tuple of Nash equilibrium mixed actions.
5569
70+
Examples
71+
--------
72+
>>> np.set_printoptions(precision=4) # Reduce the digits printed
73+
>>> bimatrix = [[(3, 3), (3, 2)],
74+
... [(2, 2), (5, 6)],
75+
... [(0, 3), (6, 1)]]
76+
>>> g = NormalFormGame(bimatrix)
77+
>>> gen = support_enumeration_gen(g)
78+
>>> for NE in gen:
79+
... print(NE)
80+
...
81+
(array([1., 0., 0.]), array([1., 0.]))
82+
(array([0.8, 0.2, 0. ]), array([0.6667, 0.3333]))
83+
(array([0. , 0.3333, 0.6667]), array([0.3333, 0.6667]))
84+
5685
"""
5786
try:
5887
N = g.N

quantecon/game_theory/vertex_enumeration.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ def vertex_enumeration(g, qhull_options=None):
4242
list(tuple(ndarray(float, ndim=1)))
4343
List containing tuples of Nash equilibrium mixed actions.
4444
45+
Examples
46+
--------
47+
>>> from pprint import pprint
48+
>>> np.set_printoptions(precision=4) # Reduce the digits printed
49+
>>> bimatrix = [[(3, 3), (3, 2)],
50+
... [(2, 2), (5, 6)],
51+
... [(0, 3), (6, 1)]]
52+
>>> g = NormalFormGame(bimatrix)
53+
>>> NEs = vertex_enumeration(g)
54+
>>> pprint(NEs)
55+
[(array([1., 0., 0.]), array([1., 0.])),
56+
(array([0. , 0.3333, 0.6667]), array([0.3333, 0.6667])),
57+
(array([0.8, 0.2, 0. ]), array([0.6667, 0.3333]))]
58+
4559
"""
4660
return list(vertex_enumeration_gen(g, qhull_options=qhull_options))
4761

@@ -64,6 +78,21 @@ def vertex_enumeration_gen(g, qhull_options=None):
6478
tuple(ndarray(float, ndim=1))
6579
Tuple of Nash equilibrium mixed actions.
6680
81+
Examples
82+
--------
83+
>>> np.set_printoptions(precision=4) # Reduce the digits printed
84+
>>> bimatrix = [[(3, 3), (3, 2)],
85+
... [(2, 2), (5, 6)],
86+
... [(0, 3), (6, 1)]]
87+
>>> g = NormalFormGame(bimatrix)
88+
>>> gen = vertex_enumeration_gen(g)
89+
>>> for NE in gen:
90+
... print(NE)
91+
...
92+
(array([1., 0., 0.]), array([1., 0.]))
93+
(array([0. , 0.3333, 0.6667]), array([0.3333, 0.6667]))
94+
(array([0.8, 0.2, 0. ]), array([0.6667, 0.3333]))
95+
6796
"""
6897
try:
6998
N = g.N

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)