# HG changeset patch # User Markus Mützel # Date 1610993079 -3600 # Mon Jan 18 19:04:39 2021 +0100 # Node ID 75a60721dd3f61b43a659054511db9619f1a342d # Parent cb9add260b2b7772fa9fb7b29756b49dae60a49b uniquetol.m: Add new function (bug #59850). * scripts/set/uniquetol.m: Add new function. * scripts/set/module.mk: Add new file to build system. * doc/interpreter/set.txi: Add docstring of new function to manual. * NEWS: Announce new function. diff -r cb9add260b2b -r 75a60721dd3f NEWS --- a/NEWS Mon Jan 18 17:08:14 2021 +0900 +++ b/NEWS Mon Jan 18 19:04:39 2021 +0100 @@ -195,6 +195,7 @@ * `sinpi` * `startsWith` * `streamribbon` +* `uniquetol` * `xtickangle` * `ytickangle` * `ztickangle` diff -r cb9add260b2b -r 75a60721dd3f doc/interpreter/set.txi --- a/doc/interpreter/set.txi Mon Jan 18 17:08:14 2021 +0900 +++ b/doc/interpreter/set.txi Mon Jan 18 19:04:39 2021 +0100 @@ -28,6 +28,8 @@ @DOCSTRING(unique) +@DOCSTRING(uniquetol) + @menu * Set Operations:: @end menu diff -r cb9add260b2b -r 75a60721dd3f scripts/set/module.mk --- a/scripts/set/module.mk Mon Jan 18 17:08:14 2021 +0900 +++ b/scripts/set/module.mk Mon Jan 18 19:04:39 2021 +0100 @@ -13,7 +13,8 @@ %reldir%/setdiff.m \ %reldir%/setxor.m \ %reldir%/union.m \ - %reldir%/unique.m + %reldir%/unique.m \ + %reldir%/uniquetol.m %canon_reldir%dir = $(fcnfiledir)/set diff -r cb9add260b2b -r 75a60721dd3f scripts/set/uniquetol.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/set/uniquetol.m Mon Jan 18 19:04:39 2021 +0100 @@ -0,0 +1,262 @@ +######################################################################## +## +## Copyright (C) 2020 The Octave Project Developers +## +## See the file COPYRIGHT.md in the top-level directory of this +## distribution or . +## +## This file is part of Octave. +## +## Octave is free software: you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Octave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, see +## . +## +######################################################################## + +## -*- texinfo -*- +## @deftypefn {} {@var{C} =} uniquetol (@var{A}, @var{tol}) +## @deftypefnx {} {@var{C} =} uniquetol (@var{A}) +## @deftypefnx {} {@var{C} =} uniquetol (@var{A}, @var{tol}, "rows") +## @deftypefnx {} {@var{C} =} uniquetol (@dots{}, "DataScale", @var{DS}, @dots{}) +## @deftypefnx {} {@var{C} =} uniquetol (@dots{}, "ByRows", true, @dots{}) +## @deftypefnx {} {[@var{C}, @var{IA}, @var{IC}] =} uniquetol (@dots{}) +## @deftypefnx {} {[@var{C}, @var{IA}, @var{IC}] =} uniquetol (@dots{}, "OutputAllIndices", true) +## Return the unique elements of @var{A} using tolerance @var{tol}. +## +## Two values, @var{u} and @var{v}, are within tolerance if +## @code {abs (@var{u} - @var{v}) <= @var{tol} * max (abs (@var{A}(:)))}. +## +## If it is unspecified, the default tolerance is 1e-12 for double precision +## input or 1e-6 for single precision input, respectively. +## +## If the input @var{A} is not a floating point type, @code{unique} is called +## without any additional options and a warning is issued. +## +## If the input @var{A} is a column vector, a column vector is returned. +## Otherwise, a row vector is returned. +## +## If the optional argument @qcode{"rows"} is given, this function returns the +## unique rows of @var{A}. Passing the property value pair +## @code{@qcode{"ByRows"}, true} does the same thing. +## +## @var{IA} and @var{IC} are column index vectors such that +## @code{@var{C} = @var{A}(@var{IA})} and @code{@var{A} = @var{C}(@var{IC})}. +## +## Additionally, the function can be called with the following optional property +## value pairs: +## +## @table @asis +## @item @qcode{"DataScale"}, @var{DS} +## The tolerance test is changed to +## @code{abs (@var{u}-@var{v}) <= @var{tol}*@var{DS}} where @var{DS} is a scalar +## unless the property @qcode{"ByRows"} is set to @code{true}. In that case, +## @var{DS} can be a scalar or a vector with a length equal to the number of +## rows in @var{A}. +## +## @item @qcode{"OutputAllIndices"}, @code{true} +## @var{IA} is a cell array that contains the indices for all elements in +## @var{A} that are within tolerance of a value in @var{C}. That is, each cell +## in @var{IA} corresponds to a value in @var{C}, and the values in each cell +## correspond to locations in @var{A}. +## @end table +## +## Example: +## +## @example +## @group +## x = (1:6)*pi; +## y = 10.^log10 (x); +## C = uniquetol ([x, y]) +## @result{} [3.1416, 6.2832, 9.4248, 12.5664, 15.7080, 18.8496] +## D = unique ([x, y]) +## @result{} [3.1416, 6.2832, 9.4248, 12.5664, 12.5664, 15.7080, +## 18.8496, 18.8496] +## @end group +## @end example +## +## @seealso{unique, union, intersect, setdiff, setxor, ismember} +## @end deftypefn + + +function [C, IA, IC] = uniquetol (A, varargin) + + if (nargin < 1) + print_usage (); + endif + + if (isempty (A)) + C = A; + IA = []; + IC = []; + return; + endif + + if (! isfloat (A)) + warning (["uniquetol: input is not a numeric, double or single " ... + "precision array. Calling unique without any additional options."]); + [C, IA, IC] = unique (A); + return; + endif + + by_rows = false; + output_all_indices = false; + data_scale = max (abs (A(! isinf (A))(:))); + size_A = size (A); + + if (nargin ()==1 || ! (isnumeric (varargin{1}) && isscalar (varargin{1}))) + if (isa (A, "double")) + tol = 1e-12; + elseif (isa (A, "single")) + tol = 1e-6; + else + error ("uniquetol: A must be a double or single precision floating point type"); + endif + else + tol = varargin{1}; + endif + + for k = 1:length(varargin) + if (isnumeric (varargin{k})) + continue; + elseif (ischar (varargin{k})) + if strcmpi (varargin{k}, "rows") + by_rows = true; + elseif (strcmpi (varargin{k}, "ByRows")) + by_rows = logical (varargin{k+1}); + elseif (strcmpi (varargin{k}, "OutputAllIndices")) + output_all_indices = logical (varargin{k+1}); + elseif (strcmpi (varargin{k}, "DataScale")) + data_scale = varargin{k+1}(:).'; + cols_data_scale = columns (data_scale); + if (! (cols_data_scale == 1 || cols_data_scale == size_A(2))) + error ("uniquetol: incorrect size of data scale"); + endif + endif + endif + endfor + + if (! by_rows) + A = A(:); + endif + + points = rows (A); + d = columns (A); + Iall = zeros (points, 1); + I = NaN (d, 1); + IA = {}; + J = NaN (d, 1); + j = 1; + ii = 0; + tol_data_scale = tol * data_scale; + + for i = 1:points + if (any (Iall == i)) + continue; + else + equ = all (abs (A - A(i,:)) <= tol_data_scale, 2); + equ(i,1) = equ(i,1) || any (! isfinite (A(i,:)), 2); + sumeq = sum (equ); + ia = find (equ); + if (output_all_indices) + IA{end+1} = ia; + endif + Iall(ii+(1:sumeq)) = ia; + I(j) = ia(1); + J(equ) = j; + ii += sumeq; + j += 1; + endif + endfor + + I(isnan (I)) = []; + J(isnan (J)) = []; + C = A(I,:); + + if (size_A(1) == 1 && size_A(2) > 1 && ! by_rows) + C = C.'; + I = I.'; + J = J.'; + endif + + if (! output_all_indices) + IA = I(1:j-1); + endif + IC = J; + +endfunction + + +%!assert (uniquetol ([1 1 2; 1 2 1; 1 1 2]), [1;2]) +%!assert (uniquetol ([1 1 2; 1 0 1; 1 1 2], 1e-12, "rows"), [1 1 2; 1 0 1]) +%!assert (uniquetol ([]), []) +%!assert (uniquetol ([1]), [1]) +%!assert (uniquetol ([1 2]), [1 2]) +%!assert (uniquetol ([1;2]), [1;2]) +%!assert (uniquetol ([1,NaN,Inf,NaN,Inf]), [1,NaN,Inf,NaN,Inf]) +%!assert (uniquetol (zeros (1,0)), zeros (1,0)) +%!assert (uniquetol (zeros (1,0), 1e-12, "rows"), zeros (1,0)) +%!assert (uniquetol ([1,2,2,3,2,4], 1e-12, "rows"), [1,2,2,3,2,4]) +%!assert (uniquetol ([1,2,2,3,2,4]), [1,2,3,4]) +%!assert (uniquetol ([1,2,2,3,2,4].', 1e-12, "rows"), [1;2;3;4]) +%!assert (uniquetol (sparse ([2,0;2,0])), sparse([2;0])) +%!assert (uniquetol (sparse ([1,2;2,3])), sparse([1;2;3])) +%!assert (uniquetol ([1,2,2,3,2,4]', 1e-12, "rows"), [1;2;3;4]) +%!assert (uniquetol (single ([1,2,2,3,2,4]), 1e-12, "rows"), single ([1,2,2,3,2,4])) +%!assert (uniquetol (single ([1,2,2,3,2,4])), single ([1,2,3,4])) +%!assert (uniquetol (single ([1,2,2,3,2,4].'), 1e-12, "rows"), single ([1;2;3;4])) +%!assert (unique (uint8 ([1,2,2,3,2,4])), uint8 ([1,2,3,4])) + +%!test +%! [y, i, j] = uniquetol ([1,1,2,3,3,3,4]); +%! assert (y, [1,2,3,4]); +%! assert (i, [1;3;4;7]); +%! assert (j, [1;1;2;3;3;3;4]); + +%!test +%! A = [2, 3, 4; 2, 3, 4]; +%! [y, i, j] = uniquetol (A, "rows"); +%! assert (y, [2, 3, 4]); +%! assert (A(i,:), y); +%! assert (y(j,:), A); + +%!test +%! x = (2:7)'*pi; +%! y = exp (1).^log (x); +%! C = uniquetol ([x; y]); +%! assert (C, x); + +%!test +%! A = [0.06, 0.21, 0.38; 0.38, 0.21, 0.39; 0.54, 0.56, 0.41; 0.46, 0.52, 0.95]; +%! B = log (exp (1).^A); +%! C = uniquetol ([A; B], "rows"); +%! assert (C, A); + +%!test +%! A = [0.06, 0.21, 0.38; 0.38, 0.21, 0.39; 0.54, 0.56, 0.41; 0.46, 0.52, 0.95]; +%! B = log (exp (1).^A); +%! C = uniquetol ([A; B], "ByRows", true); +%! assert (C, A); + +%!test +%! A = [.1 .2 .3 10]; +%! [C, IA, IC] = uniquetol (A, .1, "OutputAllIndices", true); +%! assert (C, [.1, 10]); +%! assert (IA, {(1:3)', 4}); +%! assert (IC, [1; 1; 1; 2]); + +%!test +%! x = 10^11; +%! C = uniquetol ([x, exp(log(x))], 1e-6, "DataScale", 1); +%! assert (C, [x, exp(log(x))]); + +%!error uniquetol ()