1## Copyright (C) 2011 Juan Pablo Carbajal <carbajal@ifi.uzh.ch>
2##
3## This program is free software: you can redistribute it and/or modify
4## it under the terms of the GNU General Public License as published by
5## the Free Software Foundation, either version 3 of the License, or
6## (at your option) any later version.
7##
8## This program is distributed in the hope that it will be useful,
9## but WITHOUT ANY WARRANTY; without even the implied warranty of
10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11## GNU General Public License for more details.
12##
13## You should have received a copy of the GNU General Public License
14## along with this program; see the file COPYING. If not, see
15## <https://www.gnu.org/licenses/>.
16
17## -*- texinfo -*-
18## @deftypefn  {Function File} {[@var{fhandle}, @var{fullname}] =} data2fun (@var{ti}, @var{yi})
19## @deftypefnx {Function File} {[@dots{}] =} data2fun (@dots{}, @var{property}, @var{value})
20## Create a vectorized function based on data samples using interpolation.
21##
22## The values given in @var{yi} (N-by-k matrix) correspond to evaluations of the
23## function y(t) at the points @var{ti} (N-by-1 matrix).
24## The data is interpolated and the function handle to the generated interpolant
25## is returned.
26##
27## The function accepts @var{property}-@var{value} pairs described below.
28##
29## @table @samp
30## @item file
31## Code is generated and .m file is created. The @var{value} contains the name
32## of the function. The returned function handle is a handle to that file. If
33## @var{value} is empty, then a name is automatically generated using
34## @code{tempname} and the file is created in the current directory. @var{value}
35## must not have an extension, since .m will be appended.
36## Numerical values used in the function are stored in a .mat file with the same
37## name as the function.
38##
39## @item interp
40## Type of interpolation. See @code{interp1}.
41## @end table
42##
43## @seealso{interp1}
44## @end deftypefn
45
46function [fhandle, fullfname] = data2fun (t, y, varargin)
47
48  if (nargin < 2 || mod (nargin, 2) != 0)
49    print_usage ();
50  endif
51
52  ## Check input arguments
53  interp_args = {"spline"};
54  given = struct ("file", false);
55
56  if (! isempty (varargin))
57    ## Arguments
58    interp_args = varargin;
59
60    opt_args = fieldnames (given);
61    [tf, idx] = ismember (opt_args, varargin);
62    for i=1:numel (opt_args)
63      given.(opt_args{i}) = tf(i);
64    endfor
65
66    if (given.file)
67      ## FIXME: check that file will be in the path. Otherwise fhandle(0) fails.
68
69      if (! isempty (varargin{idx(1)+1}))
70        [dir, fname] = fileparts (varargin{idx(1)+1});
71      else
72        [dir, fname] = fileparts (tempname (pwd (), "agen_"));
73      endif
74
75      interp_args(idx(1) + [0, 1]) = [];
76    endif
77
78    if (isempty (interp_args))
79      interp_args = {"spline"};
80    endif
81
82  endif
83
84  pp = interp1 (t, y, interp_args{end}, "pp");
85
86  if (given.file)
87    fullfname = fullfile (dir, [fname, ".m"]);
88    save ("-binary", [fullfname(1:end-2), ".mat"], "pp");
89
90    bodystr = ["  persistent pp\n" ...
91               "  if (isempty (pp))\n" ...
92               "    pp = load ([mfilename(), \".mat\"]).pp;\n" ...
93               "  endif\n\n" ...
94               "  z = ppval (pp, x);"];
95
96    strfunc = generate_function_str (fname, {"z"}, {"x"}, bodystr);
97
98    fid = fopen (fullfile (dir, [fname, ".m"]), "w");
99    fprintf (fid, "%s", strfunc);
100    fclose (fid);
101
102    fhandle = eval (["@", fname]);
103  else
104    fullfname = "";
105    fhandle = @(t_) ppval (pp, t_);
106  endif
107
108endfunction
109
110function str = generate_function_str (name, oargs, iargs, bodystr)
111
112  striargs = cell2mat (cellfun (@(x) [x ", "], iargs, "UniformOutput", false));
113  striargs = striargs(1:end-2);
114
115  stroargs = cell2mat (cellfun (@(x) [x ", "], oargs, "UniformOutput", false));
116  stroargs = stroargs(1:end-2);
117
118  if (! isempty (stroargs))
119    str = ["function [" stroargs "] = " name " (" striargs ")\n\n" ...
120           bodystr "\n\nendfunction"];
121  else
122    str = ["function " name " (" striargs ")\n\n" ...
123           bodystr "\n\nendfunction"];
124  endif
125
126endfunction
127
128%!shared t, y
129%! t = linspace (0, 1, 10);
130%! y = t.^2 - 2*t + 1;
131
132%!test
133%! fhandle = data2fun (t, y);
134%! assert (y, fhandle (t));
135
136%!test
137%! unwind_protect
138%!   [fhandle fname] = data2fun (t, y, "file", "testdata2fun");
139%!   yt = testdata2fun (t);
140%!   assert (y, yt);
141%!   assert (y, fhandle (t));
142%! unwind_protect_cleanup
143%!   unlink (fname);
144%!   unlink ([fname(1:end-2) ".mat"]);
145%! end_unwind_protect
146
147%!test
148%! unwind_protect
149%!   [fhandle fname] = data2fun (t, y, "file", "");
150%!   yt = testdata2fun (t);
151%!   assert (y, yt);
152%!   assert (y, fhandle (t));
153%! unwind_protect_cleanup
154%!   unlink (fname);
155%!   unlink ([fname(1:end-2) ".mat"]);
156%! end_unwind_protect
157
158%!test
159%! unwind_protect
160%!   [fhandle fname] = data2fun (t, y, "file", "testdata2fun", "interp", "linear");
161%!   yt = testdata2fun (t);
162%!   assert (y, yt);
163%!   assert (y, fhandle (t));
164%! unwind_protect_cleanup
165%!   unlink (fname);
166%!   unlink ([fname(1:end-2) ".mat"]);
167%! end_unwind_protect
168
169## Test input validation
170%!error data2fun ()
171%!error data2fun (1)
172%!error data2fun (1, 2, "file")
173