Evaluating Nested Anonymous Functions

17 views (last 30 days)
Greetings,
I am having trouble using nested anonymous functions to return a Taylor series as a function handle. Below is the function I created to calculate the 2-variable Taylor series.
function [zeroth,first,second] = Taylor2(f,x0,y0)
% This function calculates the second order Taylor expansion of a
% 2-variable anonymous function. The zero-th and first order
% approximations can also be extracted.
zeroth = f(x0,y0); % Zero-th order approximation
% Define derivatives using Newton's difference quotient
epsilon = 1e-8;
f1_x = @(x,y) (f(x+epsilon,y) - f(x,y)) / epsilon;
f1_y = @(x,y) (f(x,y+epsilon) - f(x,y)) / epsilon;
f2_x = @(x,y) (f1_x(x+epsilon,y) - f1_x(x,y)) / epsilon;
f2_y = @(x,y) (f1_y(x,y+epsilon) - f1_y(x,y)) / epsilon;
f2_xy = @(x,y) (f1_x(x,y+epsilon) - f1_x(x,y)) / epsilon;
% First order approximation
first = @(x,y) zeroth + (x-x0) * f1_x(x0,y0) + (y-y0) * f1_y(x0,y0);
% Second order approximation
second = @(x,y) first + 0.5 .* ((x-x0).^2 .* f2_x(x0,y0) + ...
2 .* (x-x0) .* (y-y0) .* f2_xy(x0,y0) + (y-y0).^2 .* f2_y(x0,y0));
end
The trouble occurs at the first order approximation, where for an input function , x0 = 1 and y0 = 1, I would expect the result to be:
first =
function_handle with value:
@(x,y) 0.9048-0.0905*(x-1)-0.0905*(y-1)
But instead I get:
first =
function_handle with value:
@(x,y)zeroth+(x-x0)*f1_x(x0,y0)+(y-y0)*f1_y(x0,y0)
How can I force the evaluation of the nested anonmyous functions and input values? I've tried using eval and @ in various combinations with no luck, and I have not found the answer after a thorough Internet and MATLAB doc search.

Accepted Answer

Jonathan Rasmussen
Jonathan Rasmussen on 8 Feb 2022
Edited: Jonathan Rasmussen on 9 Feb 2022
Simple answer: No, MATLAB does not natively have the functionality I was seeking. However, I was able to get the results I desired by using syms:
% First order approximation
syms x y
first = zeroth + (x-x0) * f1_x(x0,y0) + (y-y0) * f1_y(x0,y0);
% Second order approximation
second = first + 0.5 .* ((x-x0).^2 .* f2_x(x0,y0) + ...
2 .* (x-x0) .* (y-y0) .* f2_xy(x0,y0) + ...
(y-y0).^2 .* f2_y(x0,y0));
However, I found that after calling the updated Taylor2.m function, the returned symbolic equation did not behave well with fprintf. To avoid a messy display of the expression, I had to set
sympref('FloatingPointOutput',true);
and just display the expression in the command window by calling the assigned variable.

More Answers (2)

Steven Lord
Steven Lord on 6 Feb 2022
How do you get the anonymous function to display the coefficients (to four decimal places)? You don't, at least not without string manipulation and str2func. If you want this perhaps performing your calculations using the sym object from Symbolic Math Toolbox is a better option.
How do you get the anonymous function to evaluate the function correctly? Each anonymous function has a workspace where it stores the values of parameters (like zeroth, x0, and y0 in the case of your first function handle) for use when the anonymous function is evaluated. To confirm this call the functions function with one of your anonymous functions as input. Note that functions 1) does not allow you to modify those stored parameter values and 2) should be used for debugging and diagnostic purposes, not as part of your code's normal workflow (as stated on its documentation page.)
n = 3;
f = @(x) x.^n
f = function_handle with value:
@(x)x.^n
clear n % does not affect the value f "remembers"
f(2) % 8
ans = 8
ws = functions(f)
ws = struct with fields:
function: '@(x)x.^n' type: 'anonymous' file: '/tmp/Editor_ahhys/LiveEditorEvaluationHelperEeditorId.m' workspace: {[1×1 struct]} within_file_path: ''
ws.workspace{1}
ans = struct with fields:
n: 3
  4 Comments
Jonathan Rasmussen
Jonathan Rasmussen on 8 Feb 2022
@Steven Lord, you are rebutting my comment with a non sequitur hypothetical. Obviously if MATLAB did have the functionality I was seeking, then displaying the anonymous function loaded w/ a big matrix would be undesirable. But there are already many ways that users can get undesirable outputs in MATLAB. Garbage in, garbage out, right?
As for your point about the double precision vs 4-decimal display, my desired expected result in the original post was merely an example. MATLAB already has the capability to change the output format of variables. If MATLAB had the functionality I was seeking, then I can imagine being able to change the precision of the anonymous function coefficients.
Steven Lord
Steven Lord on 8 Feb 2022
No, I'm stating that the functionality you suggested was "intuitive" has wrinkles you hadn't considered. It's a common pattern (and not a bad one, just one that we at MathWorks need to realize and take into account): users tend to focus on what they need or want to do. MathWorks has to take into consideration not just what the user who asked for the feature wants to do but also how other users would want it to behave.
Now perhaps no user would want to display a 1000-by-1000 matrix as part of the anonymous function's display. But 10-by-10? 20-by-20? Those sound more reasonable to display in an expanded form but that would still be an awful lot of text to display. If we only display the expanded form of matrices up to a certain size, what's the threshold? Does that threshold depend on the size of your screen and/or the font you're using?
There's also a question of how much we would simplify your code. Why would we display it like this, as you showed:
@(x,y) 0.9048-0.0905*(x-1)-0.0905*(y-1)
instead of expanding out the multiplication and combining the constant terms to give this?
@(x,y) 1.0858-0.0905*x-0.0905*y
[This assumes you aren't going to call the anonymous function with data of a class that has overloaded the minus and/or mtimes operators. If you were this might not be a valid optimization depending on what those overloads do.]
At its most basic, I accept that this functionality you suggested should be part of MATLAB is intuitive. But as with most things, the devil is in the details.

Sign in to comment.


Walter Roberson
Walter Roberson on 8 Feb 2022
I could (try to) write the function for you, given the following:
  • The purpose of doing this shall be to create a readable version of the anonymous function that shall have exactly the same execution behavior as the non-readable version
  • In order to have exactly the same execution behavior, every bound variable, shall be replaced by a full precision printable representation of the variable. There shall be no method provided to change the precision of the data because different precision would lead to different results but the purpose is to have exactly the same results in all cases.
  • every bound variable shall be replaced in full every time it occurs. No provision for size limits shall be made. The purpose is to repair the deficiency that MATLAB does not already do all of this automatically all of the time, in a way that continues to generate exactly the same result
  • every bound variable, converted to text form, shall return exactly the same value in the text form. Therefore all necessary casting of variable types shall be made. In particular, in order for single and uint64 and int64 to be certain of getting back the exact same result, if necessary, each distinct value may need to be wrapped in its type, like [single(3.891),single(-1.44)] . single and int64 and uint64 that are coded around individual values are interpreted by the parser, whereas single([3.891,-1.44]) results in the construction of a double precision vector that is converted to single at run time, which does not give the same results in all cases for the conversion of double to single (and which messes up badly for int64 and uint64)
  • no abbreviations such as repmat or ones() shall be used, as those give a misleading idea of what the original code was.
  • variables shall be expanded even if they are indexed. The code @(x)x+y(1)+y(2) shall expand y in both places. As you cannot immediately index the results of the list operators then there is a problem that either this needs to generate an error or else the result is going to have to be deceptive about what the user's code was
  • multidimensional variables shall be handled. This is a small challenge because there is no direct syntax for entering multidimensional values. reshape() shall not be used, as that would give a misleading view of what the original code was. cat() of 2d arrays shall be used instead as that is the underlying operation for array construction
  • complex values shall be handled. Complex multidimensional values with an all-zero imaginary part shall raise an error condition, as it is not possible to represent that in text without a misleading representation of the values by using a call to complex()
  • negative zero shall be detected and represented, as they may be required for accurate representation of functionality (This rules out using mat2str, which does not preserve negative values)
  • sparse arrays shall be converted into calls to sparse(), preserving the free-space encoded in the sparse array
  • object oriented objects that are marked as not serializable shall generate an error message as those cannot be converted to text.
  • in the first release, most object oriented objects will lead to an error message as an implementation limitation until such time as I might figure out how to create readable text representations of objects with hidden properties and dependent properties. This might prove to be quite difficult or even impossible in the general case
  • in the first release, graphics objects based on uifigure shall not be handled, as I do not have sufficient experience with those.
  • I do have experience in doing inspection of traditional graphic objects, so the first release might potentially be able to represent some graphic objects such as axes objects passed to plot() calls. However, the code involved is likely to be several thousand lines, and it might take me a couple of months to rescue from an old version (I had major disk corruption and lost recent backups). I would need to study the extent to which I can represent the graphics objects as text each time they occur and yet avoid duplication of the graphics object. For example, if an axes is passed into plot, the readable version of it designed to have exactly the same effect will need to call axes(), but can it do so without creating a new axes?? If the code has @(y)[plot(ax,x,sin(y),plot(ax,x,cos(x)] then when I expand the ax variable to fix the error that MATLAB only passes by name instead of value in the printable representation of the function handle, then can I avoid creating two copies of the axes?? I am not presently confident it can be done at all, let alone without misleading the user about what the code was.
  6 Comments
Jonathan Rasmussen
Jonathan Rasmussen on 9 Feb 2022
...wow...you kinda made a mountain out of a mole-hill, but...thanks? I don't see how your code answers the question, and your comments are definitely information overload.
Walter Roberson
Walter Roberson on 9 Feb 2022
The code is not intended to answer the question: the code is part of a question of what your specification is .
You would like MATLAB to be able to convert anonymous functions with captured variables so that the values of the captured variables are explicit in the body of the function, instead of just showing the name of the variable.
Okay, we can potentially write that code for you. But first we need to know what the specifications are. What, exactly does the conversion function need to do?
I listed a bunch of questions at https://www.mathworks.com/matlabcentral/answers/1644275-evaluating-nested-anonymous-functions#comment_1975825 about what data types should be supported; if you could answer those, then we could get started on writing the code for you.
We will also need guideance as to the extend it is acceptable to interject new code in order for the converted function handle to have the exact same functionality as the original function handle. For example, if the code currently has
x(1) + y * x(2)
and x turns out to be a vector, then is it acceptable for the revised code to look like
subsref([3,17.3432423214121124,-3.1415926535897931,8,1.4142135623730951-1.5707963267948966j],struct('type','()','subs',1)) + y * subsref([3,17.3432423214121124,-3.1415926535897931,8,1.4142135623730951-1.5707963267948966j],struct('type','()','subs',2))
or is it acceptable if a new helper function (not written by you) were added in,
IndexAt([3,17.3432423214121124,-3.1415926535897931,8,1.4142135623730951-1.5707963267948966j],1) + y * IndexAt([3,17.3432423214121124,-3.1415926535897931,8,1.4142135623730951-1.5707963267948966j],2)
even though that could give the impression that you had coded the IndexAt call, when it is instead overhead added in to make the code work ?

Sign in to comment.

Categories

Find more on Functions in Help Center and File Exchange

Products


Release

R2021b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!