The bug report the agent was given — plus the gold fix and hidden tests, shown here for reference. The agent saw only the problem statement.
Problem statementgithub issue
evalf does not call _imp_ recursively
Example from https://stackoverflow.com/questions/41818842/why-cant-i-evaluate-a-composition-of-implemented-functions-in-sympy-at-a-point:
```
>>> from sympy.utilities.lambdify import implemented_function
>>> f = implemented_function('f', lambda x: x ** 2)
>>> g = implemented_function('g', lambda x: 2 * x)
>>> print(f( 2 ).evalf())
4.00000000000000
>>> print( g(2) .evalf())
4.00000000000000
>>> print(f(g(2)).evalf())
f(g(2))
```
The code for this is in `Function._eval_evalf`. It isn't calling evalf recursively on the return of `_imp_`.
diff --git a/sympy/utilities/tests/test_lambdify.py b/sympy/utilities/tests/test_lambdify.py--- a/sympy/utilities/tests/test_lambdify.py+++ b/sympy/utilities/tests/test_lambdify.py@@ -751,6 +751,9 @@ def test_issue_2790(): assert lambdify((x, (y, (w, z))), w + x + y + z)(1, (2, (3, 4))) == 10 assert lambdify(x, x + 1, dummify=False)(1) == 2+def test_issue_12092():+ f = implemented_function('f', lambda x: x**2)+ assert f(f(2)).evalf() == Float(16) def test_ITE(): assert lambdify((x, y, z), ITE(x, y, z))(True, 5, 3) == 5
1FAIL → PASS
43PASS → PASS
44 / 44green
The agent run
The agent's step-by-step attempt — its reasoning, the tools it called, and the patch it produced.
System promptharness.agent.build_system_prompt
You are an AI coding assistant working inside a code repository that contains a bug. Investigate the code, find the bug, and fix it.
Your working directory is: /testbed
All four tools (read_file, write_file, edit_file, run_bash) already run from that directory. Use plain relative paths; do not prefix shell commands with cd. Fix the bug by editing source files only; do not create or edit test files.
Step 01 tool use
Let me understand the problem:
1. `implemented_function` creates a custom function with an implementation (`_imp_` method)
2. When calling `evalf()` on such a function, it should evaluate the function implementation and return a numeric result
3. But when the function is composed (like `f(g(2))`), `evalf()` doesn't call `_imp_` recursively on the arguments
Let me look at the relevant code to understand the bug.
"""
There are three types of functions implemented in SymPy:
1) defined functions (in the sense that they can be evaluated) like
exp or sin; they have a name and a body:
f = exp
2) undefined function which have a name but no body. Undefined
functions can be defined using a Function class as follows:
f = Function('f')
(the result will be a Function instance)
3) anonymous function (or lambda function) which have a body (de …
Step 05 tool use
Now I can see the `_eval_evalf` method at lines around 500. Let me look at it more carefully:
```python
def _eval_evalf(self, prec):
# Lookup mpmath function based on name
fname = self.func.__name__
try:
if not hasattr(mpmath, fname):
from sympy.utilities.lambdify import MPMATH_TRANSLATIONS
fname = MPMATH_TRANSLATIONS[fname]
func = getattr(mpmath, fname)
except (AttributeError, KeyError):
try:
return Float(self._imp_(*self.args), prec)
except (AttributeError, TypeError, ValueError):
return
# Convert all args to mpf or mpc
...
```
The issue is in the try/except block. When the function doesn't have an mpmath equivalent, it tries `self._imp_(*self.args)`. But `self.args` contains the arguments as they are - for example, if we have `f(g(2))`, then `self.args` would be `(g(2),)`. The `_imp_` function receives `g(2)` as its argument, but `g(2)` is a SymPy expression, not a number. The `_imp_` function for `f` (which is `lambda x: x**2`) can't evaluate `g(2)`.
The fix should be to first call `.evalf()` on each argument before passing it to `_imp_`. So `self._imp_(*self.args)` should become something like `self._imp_(*[arg.evalf(prec) for arg in self.args])`.
Wait, but that would call evalf on the arguments. The problem is that `g(2)` is an implemented function too, and calling `.evalf()` on it should work if the bug is fixed. So we need to recursively call evalf on the args.
But wait, there's also the other path where it converts args to mpmath. Let me check if the same issue exists there.
Actually, looking at the code more carefully: the mpmath path converts args using `arg._to_mpmath(prec + 5)`. This should work fine for compositions because `_to_mpmath` should recursively evaluate.
The issue is specifically in the fallback path:
```python
return Float(self._imp_(*self.args), prec)
```
This should be:
```python
return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)
```
But wait, `Float` constructor already processes the result. Let me think about this more carefully.
Actually, the arguments need to be evaluated to floats before being passed to `_imp_`. The `_imp_` function takes numerical values, not symbolic expressions. So we need to call `evalf()` on each argument.
But there's a subtlety: what if one of the arguments is itself a function that also needs `_imp_` to be called? The recursive call to `evalf()` should handle that.
Let me also check the lambdify module to understand `implemented_function` better.
Now let me look at the `_eval_evalf` method more closely and the `implemented_function` function:
run_bash{ command: "grep -n \"implemented_function\\|_imp_\" /testbed/sympy/utilities/lambdify.py | head -40" }
tool result · run_bash
exitcode: 0
stdout: 210: implemented_function and user defined subclasses of Function. If specified,
272: >>> from sympy.utilities.lambdify import implemented_function
312: implementations, in a callable attached to the ``_imp_``
314: ``implemented_function`` factory:
316: >>> f = implemented_function(Function('f'), lambda x: x+1)
321: ``lambdify`` always prefers ``_imp_`` implementations to implementations
367: namespaces.append(_imp_namespace(expr) …
exitcode: 0
stdout: if all(getattr(t, 'is_commutative') for t in self.args):
return True
else:
return False
def _eval_evalf(self, prec):
# Lookup mpmath function based on name
fname = self.func.__name__
try:
if not hasattr(mpmath, fname):
from sympy.utilities.lambdify import MPMATH_TRANSLATIONS
fname = MPMATH_TRANSLATIONS[fname]
func = getattr(mpmath, f …
exitcode: 0
stdout: return namespace
def implemented_function(symfunc, implementation):
""" Add numerical ``implementation`` to function ``symfunc``.
``symfunc`` can be an ``UndefinedFunction`` instance, or a name string.
In the latter case we create an ``UndefinedFunction`` instance with that
name.
Be aware that this is a quick workaround, not a general method to create
special symbolic functions. If you want to create a symbolic function to b …
Step 07 tool use
Now I understand the issue. In `_eval_evalf`, when the function doesn't have a corresponding mpmath function, it falls back to:
```python
return Float(self._imp_(*self.args), prec)
```
This passes `self.args` (which are SymPy expressions) directly to `_imp_`. For `f(g(2))`, `self.args` is `(g(2),)`, and `_imp_` for `f` receives `g(2)` which is a SymPy expression, not a number.
The fix should be to evaluate the arguments (using `evalf`) before passing them to `_imp_`. So:
```python
return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)
```
But wait, there's a subtlety. We need to be careful about recursion. When we call `arg.evalf(prec)`, it will eventually call `_eval_evalf` on the argument if it's also a Function. That should work correctly since the same fix applies recursively.
Actually, let me think again. The `_imp_` function expects numerical values. After calling `evalf()` on an argument, if that argument is a function like `g(2)`, then `g(2).evalf()` should call `g`'s `_eval_evalf` which would call `g._imp_(2)` and return `Float(4, prec)`. Then `f._imp_(Float(4, prec))` would return `Float(4, prec)**2` which is `Float(16, prec)`. Wait, that's not right either.
Actually the `_imp_` function is a Python lambda like `lambda x: x**2`. When called with a Float, it would return a Float. And then `Float()` of that result would handle the precision conversion.
But actually, there's another subtlety. The `_imp_` function expects raw Python numbers, not SymPy Float objects. Let's check: `lambda x: x**2`. If x is a Float, then `x**2` would be a Float. Then `Float(Float(4, prec), prec)` would just return `Float(4, prec)` (with the given precision). Wait, that's not right for `f(g(2))` where f(x) = x^2 and g(x) = 2*x.
Let me trace through more carefully:
- `f(g(2))` → `f` has args `(g(2),)`
- `f._eval_evalf(prec)` is called
- Arguments: `[g(2).evalf(prec)]` → this calls `g._eval_evalf(prec)`
- `g._eval_evalf(prec)`: `g._imp_(*[2.evalf(prec)])` = `g._imp_(2.0)` = `4.0` → returns `Float(4.0, prec)`
- Now `f._imp_(Float(4.0, prec))` = `Float(4.0, prec)**2` = some Float
- `Float(that_result, prec)` = Float with correct precision
Actually, I realize the `_imp_` should receive plain numbers, not Float objects. But `evalf` returns Float objects. Hmm. Let me look at how `_imp_` is typically used.
Looking at `_imp_namespace` in lambdify.py:
```python
imp = getattr(func, '_imp_', None)
```
This is used to get the implementation for lambdify. When lambdify is used, the arguments are plain Python numbers.
But in `_eval_evalf`, the `_imp_` is called with `self.args` which are SymPy expressions. The `_imp_` function should be written to handle plain numbers (like `lambda x: x**2`), but it could also potentially handle SymPy types. Regardless, for the composition to work, we need the arguments to be evaluated first.
Let me check: would `_imp_` work correctly if passed a Float instead of a plain Python float? For `lambda x: x**2`, yes - `Float(4)**2` would give `Float(16)`. And then wrapping in `Float(..., prec)` would set the precision.
So the fix is to change:
```python
return Float(self._imp_(*self.args), prec)
```
to:
```python
return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)
```
Wait, but `evalf` might call `_eval_evalf` recursively. For arguments that are already numbers (like `Integer(2)`), `evalf` would just return a `Float`. For arguments that are `Function` instances like `g(2)`, `evalf` would call `_eval_evalf`. This should work recursively.
But there might be an issue: what if `_imp_` expects a specific type? For instance, what if someone writes `implemented_function('f', lambda x: x + 1)` and then calls `f(2).evalf()`? With the current code, `_imp_` receives `Integer(2)`, and `Integer(2) + 1` returns `Integer(3)`. Then `Float(Integer(3), prec)` returns a Float. If we change it to pass `Float(2, prec)` to `_imp_`, then `Float(2, prec) + 1` returns `Float(3, prec)`, and `Float(F
... [truncated 1,046 characters]
Now I understand the bug. Let me look at the exact code block in `_eval_evalf`:
exitcode: 0
stdout: def _eval_evalf(self, prec):
# Lookup mpmath function based on name
fname = self.func.__name__
try:
if not hasattr(mpmath, fname):
from sympy.utilities.lambdify import MPMATH_TRANSLATIONS
fname = MPMATH_TRANSLATIONS[fname]
func = getattr(mpmath, fname)
except (AttributeError, KeyError):
try:
return Float(self._imp_(*self.args), prec) …
Step 08 tool use
The bug is on line 510: `return Float(self._imp_(*self.args), prec)`. The problem is that `self.args` are not evaluated before being passed to `_imp_`. For composition like `f(g(2))`, the args would be `(g(2),)` — a symbolic expression, not a number.
The fix is to evaluate each argument with `evalf(prec)` before passing to `_imp_`:
```python
return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)
```
Let me verify by reproducing the bug first, then apply the fix.
exitcode: 0
stdout: f(2).evalf(): 4.00000000000000
g(2).evalf(): 4.00000000000000
f(g(2)).evalf(): f(g(2))
stderr: /testbed/sympy/core/basic.py:3: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import Mapping
/testbed/sympy/solvers/diophantine.py:3188: SyntaxWarning: "is" with a literal. Did you mean "=="?
if feasible is 1: # it's prime …
Step 09 tool use
I've confirmed the bug. Now let me fix it.
Confirmed the bug. Now let me fix it:
edit_file{ path: "/testbed/sympy/core/function.py", old_string: " return Float(self._imp_(*self.args), prec)", new_string: " return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)" }
exitcode: 0
stdout: f(2).evalf(): 4.00000000000000
g(2).evalf(): 4.00000000000000
f(g(2)).evalf(): 16.0000000000000
stderr: /testbed/sympy/core/basic.py:3: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import Mapping
/testbed/sympy/plotting/plot.py:28: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import Callable
Step 11 tool use
The fix works: `f(g(2)).evalf()` now correctly returns `16.0000000000000` (since f(x)=x² and g(x)=2x, f(g(2)) = f(4) = 16).
Let me also run the existing tests to make sure nothing is broken.
The fix works. `f(g(2)).evalf()` now correctly returns `16.0000000000000` (since `f(x)=x²`, `g(x)=2x`, so `f(g(2)) = f(4) = 16`).
Let me also run the existing tests related to evalf to make sure nothing is broken:
exitcode: 0
stdout: /testbed/sympy/core/basic.py:3: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import Mapping
/testbed/sympy/plotting/plot.py:28: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
from collections import …
Step 14 tool use
All tests pass. Let me also run the lambdify tests to make sure the fix doesn't break anything there.
All tests pass. Let me also run the lambdify tests:
All tests pass. The fix is clean and correct.
Let me summarize what the bug was and how I fixed it:
**Bug:** In `Function._eval_evalf`, the fallback path (when the function doesn't have a corresponding mpmath function) calls `self._imp_(*self.args)`, passing the raw symbolic arguments to the implementation function. When there's a composition like `f(g(2))`, `self.args` is `(g(2),)` — a symbolic expression, not a number — so `_imp_` can't compute a numerical result.
**Fix:** Evaluate each argument with `evalf(prec)` before passing it to `_imp_`. This ensures that nested function compositions are recursively evaluated to numbers before the outer `_imp_` is called.
Changed:
```python
return Float(self._imp_(*self.args), prec)
```
to:
```python
return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)
```
The fix is complete. Here's a summary:
**Bug:** In `Function._eval_evalf`, when a function has no mpmath equivalent, it falls back to calling `self._imp_(*self.args)`. The problem is that `self.args` are raw symbolic expressions — for `f(g(2))`, the args are `(g(2),)`, not `(4.0,)`. The `_imp_` function (e.g., `lambda x: x**2`) can't evaluate `g(2)` as a number.
**Fix:** Evaluate each argument with `evalf(prec)` before passing to `_imp_`, so nested function compositions are recursively evaluated to floats first.
Changed line 510 from:
```python
return Float(self._imp_(*self.args), prec)
```
to:
```python
return Float(self._imp_(*[arg.evalf(prec) for arg in self.args]), prec)
```