# can one now set a timelimit on operation in sagemath?

My question here is this: Can now (version 9) tell sagemath to set a timeout on some call and have sagemath issue an error (may be exception) if timeout expires and the call have not completed yet? I am mainly interested in integrate calls, which some take long time.

Here is an example of how this is done in Maple limits the amount of CPU time spent on a computation

restart;
integrand:=(b*x + a)^(3/2)*(d*x + c)^(5/2)/x^7;
try
timelimit(300,int(integrand,x));
print("Finished before time out, good");
catch:
print("opps, timed out");
end try;


Is it possible to do the above directly in sagemath without me having to program the timelimit myself using Process and Queues? I am using sagemath 9 on Linux.

update

I am getting stack dump when implementing the alarm method shown in the answer below. I am not sure why that is. May be I am making an error somewhere. Below is a MWE to reproduce it.

I created a file build_fricas_new_timeout_one_integral.sage with content

#!/usr/bin/env sage

from sage.all import *
from cysignals.alarm import alarm, AlarmInterrupt, cancel_alarm

var('x a b')

def doTheIntegration():
integrand = tan(x)/(a^3+b^3*tan(x)^2)^(1/3)

fricas.setSimplifyDenomsFlag(fricas.true)
anti=integrate(integrand,x,algorithm="fricas")
return anti

try:
alarm(20)
anti = doTheIntegration()
except AlarmInterrupt:
print("Timed out")
else:
print("Completed OK, anti=",anti)
cancel_alarm()


Then called it as follows sage ./build_fricas_new_timeout_one_integral.sage then I get this on the screen

>sage ./build_fricas_new_timeout_one_integral.sage
Interrupting FriCAS...
Traceback (most recent call last):
File "/usr/lib/python3.8/site-packages/sage/interfaces/expect.py", line 986, in _eval_line
E.expect(self._prompt)
File "/usr/lib/python3.8/site-packages/pexpect/spawnbase.py", line 343, in expect
return self.expect_list(compiled_pattern_list,
File "/usr/lib/python3.8/site-packages/pexpect/spawnbase.py", line 372, in expect_list
return exp.expect_loop(timeout)
File "/usr/lib/python3.8/site-packages/pexpect/expect.py", line 169, in expect_loop
File "/usr/lib/python3.8/site-packages/pexpect/pty_spawn.py", line 500, in read_nonblocking
if (timeout != 0) and select(timeout):
File "/usr/lib/python3.8/site-packages/pexpect/pty_spawn.py", line 450, in select
return select_ignore_interrupts([self.child_fd], [], [], timeout)[0]
File "/usr/lib/python3.8/site-packages/pexpect/utils.py", line 143, in select_ignore_interrupts
return select.select(iwtd, owtd, ewtd, timeout)
File "src/cysignals/signals.pyx", line 320, in cysignals.signals.python_check_interrupt
cysignals.signals.AlarmInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "./build_fricas_new_timeout_one_integral.sage.py", line 22, in <module>
anti = doTheIntegration()
File "./build_fricas_new_timeout_one_integral.sage.py", line 17, in doTheIntegration
anti=integrate(integrand,x,algorithm="fricas")
File "/usr/lib/python3.8/site-packages/sage/misc/functional.py", line 753, in integral
return x.integral(*args, **kwds)
File "sage/symbolic/expression.pyx", line 12391, in sage.symbolic.expression.Expression.integral (build/cythonized/sage/symbolic/expression.cpp:64575)
File "/usr/lib/python3.8/site-packages/sage/symbolic/integration/integral.py", line 927, in integrate
return integrator(expression, v, a, b)
File "/usr/lib/python3.8/site-packages/sage/symbolic/integration/external.py", line 386, in fricas_integrator
result = ex.integrate(v)
File "/usr/lib/python3.8/site-packages/sage/interfaces/interface.py", line 680, in __call__
return self._obj.parent().function_call(self._name, [self._obj] + list(args), kwds)
File "/usr/lib/python3.8/site-packages/sage/interfaces/interface.py", line 601, in function_call
return self.new(s)
File "/usr/lib/python3.8/site-packages/sage/interfaces/interface.py", line 370, in new
return self(code)
File "/usr/lib/python3.8/site-packages/sage/interfaces/interface.py", line 296, in __call__
return cls(self, x, name=name)
File "/usr/lib/python3.8/site-packages/sage/interfaces/expect.py", line 1471, in __init__
self._name = parent._create(value, name=name)
File "/usr/lib/python3.8/site-packages/sage/interfaces/interface.py", line 501, in _create
self.set(name, value)
File "/usr/lib/python3.8/site-packages/sage/interfaces/fricas.py", line 589, in set
output = self.eval(cmd, reformat=False)
File "/usr/lib/python3.8/site-packages/sage/interfaces/fricas.py", line 847, in eval
output = Expect.eval(self, code, strip=strip,
File "/usr/lib/python3.8/site-packages/sage/interfaces/expect.py", line 1384, in eval
return '\n'.join([self._eval_line(L, allow_use_file=allow_use_file, **kwds)
File "/usr/lib/python3.8/site-packages/sage/interfaces/expect.py", line 1384, in <listcomp>
return '\n'.join([self._eval_line(L, allow_use_file=allow_use_file, **kwds)
File "/usr/lib/python3.8/site-packages/sage/interfaces/expect.py", line 1017, in _eval_line
self._keyboard_interrupt()
File "/usr/lib/python3.8/site-packages/sage/interfaces/expect.py", line 1039, in _keyboard_interrupt
raise KeyboardInterrupt("Ctrl-c pressed while running %s" % self)
KeyboardInterrupt: Ctrl-c pressed while running FriCAS
>


Am I doing something wrong in using the alarm method?

edit retag close merge delete

Sort by » oldest newest most voted

One way is to use alarm and AlarmInerrupt:

sage: from cysignals.alarm import alarm, AlarmInterrupt, cancel_alarm


If computation takes more than 5 seconds, computation is stopped:

sage: try:
....:     alarm(5)
....:     factor(2^1000-1)
....: except AlarmInterrupt:
....:     print('opps, timed out!')
....: else:
....:     cancel_alarm()
....:
opps, timed out!


If computation takes less than 5 seconds, it is not stopped:

sage: try:
....:     alarm(5)
....:     factor(2^100-1)
....: except AlarmInterrupt:
....:     print('opps, timed out!')
....: else:
....:     cancel_alarm()
....:
3 * 5^3 * 11 * 31 * 41 * 101 * 251 * 601 * 1801 * 4051 * 8101 * 268501


It is important to cancel the alarm if the computation succeeds (as done above in the else clause) or otherwise the AlarmInterrupt will be raised later on.

EDIT: In some cases including the question asked here, you may need to also catch KeyboardInterrupt exception:

#!/usr/bin/env sage

from sage.all import *
from cysignals.alarm import alarm, AlarmInterrupt, cancel_alarm

var('x a b')

def doTheIntegration():
integrand = tan(x)/(a^3+b^3*tan(x)^2)^(1/3)

fricas.setSimplifyDenomsFlag(fricas.true)
anti=integrate(integrand,x,algorithm="fricas")
return anti

try:
alarm(20)
anti = doTheIntegration()
except (AlarmInterrupt,KeyboardInterrupt):
print("Timed out")
else:
print("Completed OK, anti=",anti)
cancel_alarm()

more

Thanks, but when testing this method I found a problem calling integrate. I get During handling of the above exception, another exception occurred:. since space here is too small, I will now update this in my question to give an example how to reproduce it.

( 2020-03-24 04:41:41 -0500 )edit
1

I would not trust the results of a sage session that regularly has been interrupted (by alarms or user interrupts): while the code generally tries to block interrupts in critical parts and then deal with them once the system is in a more consistent state, I would expect sage doesn't entirely succeed: I expect in some cases the system will be left in an inconsistent state (there are bugs, so this is also true without interrupts...)

If you want to time-limit computations "safely", I would trust a fork-and-kill method more: then you at least discard the state of the computing processif it didn't complete. I think that indeed needs you to put some plumbing in place yourself, but that could be wrapped up in convenient routines.

( 2020-03-24 11:10:57 -0500 )edit
1

I updated my question to fix the issue when running with fricas code (I think fricas catches the AlarmInterrupt and replaces it by a KeyboardInterrupt).

( 2020-03-24 12:48:14 -0500 )edit