├── +cmt ├── bb2z.m ├── boundbox.m ├── clearPrefs.m ├── dispPrefs.m ├── plotbox.m └── runTests.m ├── .gitignore ├── LICENSE ├── MILESTONES ├── README.md ├── TODO ├── adaptplot.m ├── aspectequal.m ├── cinvcurve.m ├── circle.m ├── circleRegion.m ├── closedcurve.m ├── cmtobject.m ├── cmtplot.m ├── conformalmap.m ├── corner.m ├── disk.m ├── diskex.m ├── doc └── dev_guide.tex ├── ellipse.m ├── gridcurves.m ├── gridset.m ├── homog.m ├── infvertex.m ├── isinpoly.m ├── mobius.m ├── optset.m ├── plotset.m ├── polygon.m ├── region.m ├── spec_mobius_grids.m ├── spec_splinep.m ├── spec_splinepwp.m ├── spec_szego.m ├── splinep.m ├── splinepwp.m ├── szego.m ├── szmap.m ├── szset.m ├── test ├── masterTest.m ├── testCircle.m ├── testHomog.m ├── testMobius.m ├── testPolygon.m └── testRegion.m └── unitdisk.m /+cmt/bb2z.m: -------------------------------------------------------------------------------- 1 | function z = bb2z(box) 2 | %BB2Z axis bounding box to vertices. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | 10 | z = complex(box([1, 2, 2, 1]), box([3, 3, 4, 4])); 11 | z = z(:); 12 | 13 | end 14 | -------------------------------------------------------------------------------- /+cmt/boundbox.m: -------------------------------------------------------------------------------- 1 | function box = boundbox(points) 2 | % BOUNDBOX calculates the bounding box around points. 3 | % 4 | % box = boundbox(points) calculates a bounding box around a set of points 5 | % in the complex plane, and returns coordinates in AXIS format. 6 | % 7 | % See also axis, plotbox. 8 | 9 | % This file is a part of the CMToolkit. 10 | % It is licensed under the BSD 3-clause license. 11 | % (See LICENSE.) 12 | 13 | % Copyright Toby Driscoll, 2014. 14 | 15 | box([1 3]) = min([real(points(:)) imag(points(:))], [], 1); 16 | box([2 4]) = max([real(points(:)) imag(points(:))], [], 1); 17 | 18 | end 19 | -------------------------------------------------------------------------------- /+cmt/clearPrefs.m: -------------------------------------------------------------------------------- 1 | function clearPrefs 2 | % Remove appdata prefs storage. 3 | % 4 | % Needed since "clear classes" complains if any objects are stored 5 | % in appdata. 6 | 7 | % This file is a part of the CMToolkit. 8 | % It is licensed under the BSD 3-clause license. 9 | % (See LICENSE.) 10 | 11 | % Copyright Toby Driscoll, 2014. 12 | 13 | try 14 | rmappdata(0, 'cmt_prefs') 15 | catch err 16 | if verLessThan('matlab', '8.4') 17 | msgid = 'MATLAB:HandleGraphics:Appdata:InvalidNameAppdata'; 18 | else 19 | msgid = 'MATLAB:HandleGraphics:Appdata:InvalidPropertyName'; 20 | end 21 | if strcmp(err.identifier, msgid) 22 | % Nothing there, that's ok. 23 | return 24 | else 25 | rethrow(err) 26 | end 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /+cmt/dispPrefs.m: -------------------------------------------------------------------------------- 1 | function dispPrefs 2 | % Preference dump to console. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | 10 | prefs = getappdata(0, 'cmt_prefs'); 11 | if isempty(prefs) 12 | fprintf('No CMT preferences found.\n') 13 | return 14 | end 15 | 16 | fn = fieldnames(prefs); 17 | for k = 1:numel(fn) 18 | fprintf(['\n==========================\n' ... 19 | ' For class %s:\n' ... 20 | '--------------------------\n'], fn{k}) 21 | disp(prefs.(fn{k})) 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /+cmt/plotbox.m: -------------------------------------------------------------------------------- 1 | function box = plotbox(points, scale) 2 | % PLOTBOX returns padded axis coordinates around points. 3 | % 4 | % box = plotbox(points) calculates a 1-by-4 array to pass to AXIS which 5 | % sets a padded square box around the given points. 6 | % 7 | % box = plotbox(points, scale) allows setting the padding scale, which 8 | % defaults to 1.2 times the largest axis of the bounding box. 9 | % 10 | % See also axis, boundbox. 11 | 12 | % This file is a part of the CMToolkit. 13 | % It is licensed under the BSD 3-clause license. 14 | % (See LICENSE.) 15 | 16 | % Copyright Toby Driscoll, 2014. 17 | 18 | if nargin < 2 || isempty(scale) 19 | scale = 1.2; 20 | end 21 | 22 | box = cmt.boundbox(points); 23 | 24 | dbox = scale/2*max(diff(box(1:2)), diff(box(3:4)))*[-1 1]; 25 | 26 | box(1:2) = mean(box(1:2)) + dbox; 27 | box(3:4) = mean(box(3:4)) + dbox; 28 | 29 | end 30 | -------------------------------------------------------------------------------- /+cmt/runTests.m: -------------------------------------------------------------------------------- 1 | function result = runTests() 2 | %runTests runs CMT unit test suite. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | 10 | import matlab.unittest.TestSuite; 11 | 12 | suite_all_tests = TestSuite.fromFolder('test'); 13 | status = run(suite_all_tests); 14 | 15 | if nargout 16 | result = status; 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VIM swap files 2 | *.swp 3 | # "publish" output 4 | **/html/ 5 | # latex files 6 | *.aux 7 | *.log 8 | *.out 9 | *.pdf 10 | *.toc 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Tobin A. Driscoll 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the University of Delaware, nor the names of its 15 | contributors or the names of their employers, may be used to endorse or promote 16 | products derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MILESTONES: -------------------------------------------------------------------------------- 1 | 2 | release 0.1: 3 | 4 | * Basic Mobius transformations work. 5 | * Composition 6 | * Plotting 7 | 8 | * Test framework for classes. 9 | * Each class has some basic test class 10 | 11 | * (Semi-)consistent plotting framework. 12 | 13 | 14 | release 0.2: 15 | 16 | * Help text for classes and functions more standardized and complete. 17 | 18 | * Standardize RSPLOT like PLOT. 19 | 20 | * Maps based on Szego kernel. 21 | * Periodic spline class, ellipse, (other smooth classes?). 22 | * Basic fft map (Taylor series for maps from disk). 23 | 24 | 25 | release 1.0: 26 | 27 | * ? 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | conformalmapping 2 | ================ 3 | 4 | Conformal Mapping Toolkit (CMToolkit) 5 | 6 | Copyright Toby Driscoll, 2014. 7 | Licensed under the BSD 3-clause license. See LICENSE. 8 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | For milestone 'release 0.1': 3 | 4 | * ? 5 | 6 | 7 | For milestone 'release 0.2': 8 | 9 | * Write periodic spline class (< closedcurve). 10 | 11 | * Write ellipse class (< closedcurve). 12 | 13 | * Write Szego kernel class. 14 | 15 | * Write fftmap class (< conformalmap). 16 | 17 | -------------------------------------------------------------------------------- /adaptplot.m: -------------------------------------------------------------------------------- 1 | function [out1,out2] = adaptplot(fun,tspan,varargin) 2 | %ADAPTPLOT Adaptively plot an explicit or parametric curve. 3 | % ADAPTPLOT(FUN,TSPAN) adaptively selects values of t in the interval 4 | % TSPAN in order to produce a nice-looking plot. FUN should accept column 5 | % vector inputs for t and produce an array X=FUN(T), where each column of 6 | % X is one component of the curve. If size(X,2)==1, the plot is X versus 7 | % T. If size(X,2) is 2 or 3, the curve is parametrically defined by the 8 | % columns of X. 9 | % 10 | % ADAPTPLOT(FUN,TSPAN,'trace',DELAY) shows the iterative addition of 11 | % points, with an optionally specified pause between iterations. 12 | % 13 | % HAN = ADAPTPLOT(FUN,TSPAN) returns a handle to the resulting line. 14 | % 15 | % [T,X] = ADAPTPLOT(FUN,TSPAN) returns the computed T values and X arrays 16 | % without producing any graphics. 17 | % 18 | % Examples: 19 | % adaptplot( @humps, [0,1] ) 20 | % adaptplot( @humps, [0,1], 'trace', 0.75) 21 | % adaptplot( @(t) exp(-3*sin(t)+2*cos(4*t)), [2 8], 'trace', 0.75) 22 | % adaptplot( @(t) [cos(t) sin(t)], [-pi pi] ) 23 | % adaptplot( @(t) [t.*cos(t), exp(t).*sin(t), t], [0 6*pi] ) 24 | % 25 | % See also FPLOT. 26 | 27 | % This file is a part of the CMToolbox. 28 | % It is licensed under the BSD 3-clause license. 29 | % (See LICENSE.) 30 | 31 | % Copyright Toby Driscoll, 2006. All rights reserved. 32 | % Version 1.0, 02 June 2006 33 | 34 | %% Preliminaries 35 | [traceflag,delay] = optargs({'no',0.1},varargin); 36 | dotrace = isequal(traceflag,'trace'); 37 | if dotrace, shg, end 38 | 39 | x = feval(fun,tspan(1)); 40 | ndim = size(x,2); 41 | if nargout < 2 42 | % We are expected to do some plotting. Start with a high-level plot 43 | % command to get the axes behavior right, then use line() to avoid 44 | % holding issues. 45 | makeplot = true; 46 | if ndim < 3 47 | allhand = plot(NaN,NaN,'k.-'); %,'erasemode','back'); 48 | else 49 | allhand = plot3(NaN,NaN,NaN,'k.-'); %,'erasemode','back'); 50 | end 51 | badhand = line(NaN,NaN,'color','r','marker','.','linestyle','none'); %,... 52 | % 'erasemode','none'); 53 | else 54 | makeplot = false; 55 | end 56 | 57 | fun = fcnchk(fun); % this is in FPLOT 58 | 59 | % If the axis limits are frozen, use them to determine the stopping 60 | % criterion. Otherwise, it will be based on the size of the graph itself. 61 | if strcmp(get(gca,'xlimmode'),'manual') & ishold 62 | axlim = axis; 63 | axlim = reshape( axlim, [2 length(axlim)/2] ); 64 | diam = max( diff(axlim,[],1) ); 65 | axfixed = true; 66 | else 67 | diam = 0; 68 | axfixed = false; 69 | end 70 | 71 | maxdepth = 12; % number of iterations of refinement 72 | n = 14; % initial number of points (prime # of intervals) 73 | tol = 2e-3; % acceptable error relative to diam 74 | 75 | %% Begin refinement 76 | t = linspace(tspan(1),tspan(2),n)'; 77 | x = feval(fun,t); 78 | refine = true; 79 | depth = 1; 80 | tnew = []; xnew = zeros(0,ndim); 81 | 82 | while any(refine) & (depth < maxdepth) 83 | 84 | % Update trace plot 85 | if makeplot & dotrace 86 | updateplot(allhand,t,x,ndim) 87 | updateplot(badhand,tnew,xnew,ndim) 88 | drawnow, pause(delay) 89 | end 90 | 91 | % Perform forward/backward linear extrapolation from each pair of 92 | % neighboring points 93 | dx = diff(x,[],1); dt = diff(t); 94 | xf = x(1:n-2,:) + dx(1:n-2,:) .* ... 95 | repmat( (t(3:n)-t(1:n-2))./dt(1:n-2), [1 ndim] ); 96 | xb = x(3:n,:) + dx(2:n-1,:) .* ... 97 | repmat( (t(1:n-2)-t(3:n))./dt(2:n-1), [1 ndim] ); 98 | 99 | % Errors in forward and backward differences 100 | ef = ptdist( xf, x(3:n,:) ); 101 | eb = ptdist( xb, x(1:n-2,:) ); 102 | 103 | % Make refinement decisions 104 | refine = false(n,1); 105 | refine(1:n-2) = (eb > tol*diam); 106 | refine(3:n) = refine(3:n) | (ef > tol*diam); 107 | 108 | % Offending points add new t values 1/3 of the way toward either side 109 | ref1 = find( refine(1:n-1) ); 110 | ref2 = find( refine(2:n) ); 111 | tnew = [ t(ref1) + dt(ref1)/3; t(1+ref2) - dt(ref2)/3 ]; 112 | xnew = feval(fun,tnew); 113 | 114 | % Sort in the new entries 115 | [t,idx] = sort( [t;tnew] ); 116 | x = [x;xnew]; x = x(idx,:); 117 | 118 | % Update parameters 119 | n = length(t); 120 | depth = depth+1; 121 | if ~axfixed 122 | diam = abs( max( max(x,[],1) - min(x,[],1) ) ); 123 | end 124 | end 125 | 126 | %% Wrap up 127 | if makeplot 128 | set(allhand,'marker','none','color','b') 129 | % set(allhand,'erasemode','normal','marker','none','color','b') 130 | updateplot(allhand,t,x,ndim) 131 | % set(badhand,'erasemode','normal') 132 | delete(badhand) 133 | end 134 | 135 | if nargout==2 136 | out1 = t; out2 = x; 137 | elseif nargout==1 138 | out1 = allhand; 139 | end 140 | 141 | end 142 | 143 | %% Subfunctions 144 | 145 | function d = ptdist(x,y) 146 | d = sqrt( sum( abs(x-y).^2, 2 ) ); 147 | end 148 | 149 | function updateplot(hand,t,x,ndim) 150 | if ndim==1 151 | set(hand,'xdata',t,'ydata',x) 152 | elseif ndim==2 153 | set(hand,'xdata',x(:,1),'ydata',x(:,2)) 154 | else 155 | set(hand,'xdata',x(:,1),'ydata',x(:,2),'zdata',x(:,3)) 156 | end 157 | end 158 | 159 | function varargout = optargs(default,arg) 160 | % Use arg to override defaults 161 | varargout = default; 162 | idx = find( ~cellfun('isempty',arg) ); 163 | varargout(idx) = arg(idx); 164 | end 165 | 166 | -------------------------------------------------------------------------------- /aspectequal.m: -------------------------------------------------------------------------------- 1 | function aspectequal(ah) 2 | % ASPECTEQUAL sets data aspect ratio to equal on all dimensions. 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | if ~nargin || isempty(ah) 12 | ah = gca; 13 | end 14 | 15 | set(ah, 'dataaspectratio', [1, 1, 1]) 16 | -------------------------------------------------------------------------------- /cinvcurve.m: -------------------------------------------------------------------------------- 1 | classdef cinvcurve < closedcurve 2 | % CINVCURVE class represents the conjugate inverse of a curve. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | properties 12 | theCurve 13 | confCenter = 0 14 | end 15 | 16 | methods 17 | function C = cinvcurve(curve, confCenter) 18 | if ~nargin 19 | return 20 | end 21 | 22 | if ~isa(curve, 'closedcurve') 23 | error('CMT:InvalidArgument', 'Expected a closedcurve object.') 24 | end 25 | if nargin > 1 26 | if numel(confCenter) == 1 && isfinite(confCenter) 27 | C.confCenter = confCenter; 28 | else 29 | error('CMT:InvalidArgument', ... 30 | 'Expected a finite scalar for a conformal center.') 31 | end 32 | end 33 | 34 | C.theCurve = curve; 35 | end 36 | 37 | function disp(C) 38 | fprintf('conjugate inverse of curve:\n') 39 | disp(C.theCurve) 40 | end 41 | 42 | function z = point(C, t) 43 | z = 1./conj(point(C.theCurve, t) - C.confCenter); 44 | end 45 | 46 | function zt = tangent(C, t) 47 | zt = -conj(tangent(C.theCurve, t))./... 48 | conj(point(C.theCurve, t) - C.confCenter).^2; 49 | end 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /circle.m: -------------------------------------------------------------------------------- 1 | classdef circle < closedcurve 2 | % CIRCLE is a generalized circle class. 3 | % 4 | % C = circle(center, radius) 5 | % Creates a circle with given center and radius. 6 | % 7 | % C = circle([z1, z2, z3]) 8 | % Creates a generalized circle passing through the three given points. 9 | 10 | % This file is a part of the CMToolbox. 11 | % It is licensed under the BSD 3-clause license. 12 | % (See LICENSE.) 13 | 14 | % Copyright Toby Driscoll, 2014. 15 | % (Re)written by Everett Kropf, 2014, 16 | % adapted from code by Toby Driscoll, originally 20??. 17 | 18 | properties 19 | points 20 | circCenter 21 | circRadius 22 | interiorPoint 23 | end 24 | 25 | properties(Hidden,Constant) 26 | ei2pi = exp(2i*pi*(0:199)'/199) 27 | end 28 | 29 | methods 30 | function gc = circle(varargin) 31 | if ~nargin 32 | return 33 | end 34 | 35 | if isa(varargin{1}, 'circle') 36 | gc = varargin{1}; 37 | return 38 | end 39 | 40 | badargs = true; 41 | switch nargin 42 | case 1 43 | z3 = varargin{1}; 44 | if isa(z3, 'double') && numel(z3) == 3 45 | badargs = false; 46 | 47 | % Deduce center and radius. 48 | % Use standardmap directly to avoid vicious circularity, since mobius 49 | % constructs a circle when given two 3-tuples. 50 | M = mobius(mobius.standardmap(z3)\mobius.standardmap([1, 1i, -1])); 51 | zi = pole(M); 52 | if abs(abs(zi) - 1) < 10*eps 53 | % Infinite radius case. 54 | center = nan; 55 | radius = inf; 56 | % If Inf was given, make it the last point. 57 | if isreal(z3) 58 | z3 = complex(z3); 59 | end 60 | z3 = sort(z3); 61 | else 62 | % Inverse of zi maps to center. 63 | center = M(1/conj(zi)); 64 | radius = abs(z3(1) - center); 65 | end 66 | 67 | % Find a point in the interior of the curve. For a line, this is a 68 | % point to the "left" as seen by following the given points. 69 | if ~isinf(radius) 70 | interiorpt = center; 71 | else 72 | tangent = diff(z3(1:2)); 73 | interiorpt = z3(1) + 1i*tangent; 74 | end 75 | end 76 | 77 | case 2 78 | [center, radius] = varargin{:}; 79 | centercond = isa(center, 'double') && numel(center) == 1 ... 80 | && ~(isnan(center) || isinf(center)); 81 | radiuscond = isa(radius, 'double') && numel(radius) == 1 ... 82 | && ~(isnan(radius) || isinf(radius) || radius < 0); 83 | if (centercond && radiuscond) 84 | badargs = false; 85 | 86 | if radius > 0 87 | z3 = center + radius*exp(1i*pi*[0, 0.5, 1]); 88 | else 89 | z3 = []; % Degenerate circle. 90 | end 91 | interiorpt = center; 92 | end 93 | end 94 | if badargs 95 | error('Circle takes a vector of 3 points or a center and radius.') 96 | end 97 | 98 | gc.points = z3; 99 | gc.circCenter = center; 100 | gc.circRadius = radius; 101 | gc.interiorPoint = interiorpt; 102 | end 103 | 104 | function gc = apply(gc, m) 105 | if ~isa(m, 'mobius') 106 | error('CMT:NotDefined', ... 107 | 'Expected a mobius transformation.') 108 | end 109 | 110 | gc = circle(m(gc.points)); 111 | end 112 | 113 | function z = center(gc) 114 | z = gc.circCenter; 115 | end 116 | 117 | function disp(gc) 118 | if isinf(gc) 119 | fprintf('circle (generalized) as a line,\n') 120 | else 121 | fprintf('circle with center %s and radius %s,\n', ... 122 | num2str(gc.circCenter), num2str(gc.circRadius)) 123 | end 124 | if isempty(gc.points) 125 | fprintf('\n(degenerate circle)\n\n') 126 | else 127 | fprintf('\npassing through points:\n\n') 128 | disp(gc.points(:)) 129 | end 130 | end 131 | 132 | function d = dist(gc, z) 133 | % Distance between point and circle. 134 | if ~isinf(gc) 135 | v = z - gc.circCenter; 136 | d = abs(abs(v) - gc.circRadius); 137 | else 138 | v = z - gc.points(1); 139 | s = sign(1i*diff(gc.points(1:2))); 140 | d = abs(real(v)*real(s) + imag(v)*imag(s)); 141 | end 142 | end 143 | 144 | function out = fill(gc, varargin) 145 | args = cmtplot.fillargs; 146 | z = gc.center + gc.radius*gc.ei2pi; 147 | h = fill(real(z), imag(z), args{:}, varargin{:}); 148 | if nargout 149 | out = h; 150 | end 151 | end 152 | 153 | function z = intersect(gc1, gc2) 154 | % Calculate circle intersections. 155 | 156 | % Map first circle to the real axis. 157 | M = mobius(point(gc1, [1/3, 2/3, 1]), [-1, 1, Inf]); 158 | gc = M(gc2); 159 | if isinf(gc) 160 | % Intersect real axis with a line. 161 | tau = tangent(gc); 162 | p = gc.points(1); 163 | if abs(imag(tau)) > 100*eps 164 | t = -imag(p)/imag(tau); 165 | z = real(p) + t*real(tau); 166 | else 167 | warning(['Circles are close to tangency.\nIntersection', ... 168 | ' problem is not well conditioned.']) 169 | z = []; 170 | end 171 | z = [z, inf]; 172 | else 173 | % Intersect real axis with a circle. 174 | rat = -imag(gc.circCenter)/gc.circRadius; 175 | if abs(abs(rat) - 1) < 100*eps 176 | warning(['Circles are close to tangency.\nIntersection', ... 177 | ' problem is not well conditioned.']) 178 | end 179 | theta = asin(rat); % find one intersection 180 | theta = theta(isreal(theta)); % may not have one 181 | theta = unique([theta, pi - theta]); % may have a second 182 | z = real(gc.circCenter + gc.circRadius*exp(1i*theta)); 183 | end 184 | z = feval(inv(M), z); 185 | end 186 | 187 | function tf = isinf(gc) 188 | tf = isinf(gc.circRadius); 189 | end 190 | 191 | function tf = isinside(gc, z) 192 | if isinf(gc) 193 | z = (z - gc.points(1))/tangent(gc, z); 194 | tf = imag(z) > 0; 195 | else 196 | tf = abs(z - gc.circCenter) < gc.circRadius; 197 | end 198 | end 199 | 200 | function gc = minus(gc, z) 201 | if isa(z, 'circle') 202 | gc = plus(-gc, z); 203 | else 204 | gc = plus(gc, -z); 205 | end 206 | end 207 | 208 | function gc = mtimes(gc, z) 209 | if isa(z, 'circle') 210 | [z, gc] = deal(gc, z); 211 | end 212 | 213 | if isinf(z) 214 | error('Must scale by a finite number.') 215 | end 216 | 217 | gc.points = gc.points*z; 218 | gc.circCenter = gc.circCenter*z; 219 | gc.circRadius = gc.circRadius*abs(z); 220 | end 221 | 222 | function gc = plus(gc, z) 223 | if isa(z, 'circle') 224 | [z, gc] = deal(gc, z); 225 | end 226 | if isinf(z) 227 | error('Must translate by a finite number.') 228 | end 229 | 230 | gc.points = gc.points + z; 231 | gc.circCenter = gc.circCenter + z; 232 | end 233 | 234 | function z = point(gc, t) 235 | if ~isinf(gc.circRadius) 236 | theta = angle(gc.points(1) - gc.circCenter) + 2*pi*t; 237 | z = gc.circCenter + gc.circRadius*exp(1i*theta); 238 | else 239 | % Use homogeneous coordinates to define a reasonable interpolant. 240 | tangent = diff(gc.points(1:2)); % must be finite 241 | upper = 2*tangent*(t - 1/2); 242 | lower = 4*t.*(1 - t); 243 | z = double(homog(gc.points(1)) + homog(upper, lower)); 244 | end 245 | end 246 | 247 | function r = radius(gc) 248 | r = gc.circRadius; 249 | end 250 | 251 | function zt = tangent(gc, t) 252 | if isinf(gc) 253 | zt = diff(gc.points(1:2)); 254 | else 255 | zt = 1i*(point(gc, t) - gc.circCenter); 256 | end 257 | end 258 | 259 | function gc = uminus(gc) 260 | gc.points = -gc.points; 261 | gc.circCenter = -gc.circCenter; 262 | end 263 | end 264 | 265 | methods(Hidden) 266 | function h = plotCurve(gc) 267 | if isinf(gc) 268 | % For a line, we will use a polygon. This employs the truncation 269 | % mechanism that gives us something usable with plotting regions. 270 | h = plot(polygon([gc.points(1), infvertex(tangent(gc), -tangent(gc))])); 271 | else 272 | % Circle!! 273 | z = gc.center + gc.radius*gc.ei2pi; 274 | h = plot(real(z), imag(z)); 275 | end 276 | end 277 | end 278 | 279 | end 280 | -------------------------------------------------------------------------------- /circleRegion.m: -------------------------------------------------------------------------------- 1 | classdef circleRegion < region 2 | %circleRegion represents a region bounded by circles 3 | % 4 | % C = circleRegion(clist) 5 | % C = circleRegion(circle1, circle2, ...) 6 | % Uses circles in cell array clist, or alternatively a list of circles as 7 | % individual arguments, to create a region with zero or more circles as 8 | % boundaries. If the first circle bounds the following circles, the region 9 | % is considered bounded. It's an unbounded region otherwise. 10 | % 11 | % This class represents one of two types of regions: 12 | % 1. A region bounded by a circle which contains one or more circular 13 | % holes. 14 | % 2. An unbounded region containing one or more circular holes. 15 | % In either case none of the circular boundaries intersect, including 16 | % tangentialy. 17 | % The single circle case is ambiguous, thus the class constructor defaults 18 | % to the unbounded case for a single circle. In this case, and this case 19 | % only, one may use the following to convert to a bounded region: 20 | % 21 | % C = circleRegion(circle(center, radius)); 22 | % C.bounded = true; 23 | % 24 | % See also circle, region. 25 | 26 | % This file is a part of the CMToolit. 27 | % It is licensed under the BSD 3-clause license. 28 | % (See LICENSE.) 29 | 30 | % Copyright Toby Driscoll, 2014. 31 | % E. Kropf, 2014 32 | 33 | % UNDOCUMENTED: 34 | % C = circleRegion(..., 'nocheck') 35 | % Skips the bounded/unbounded/intersect check. Use with extreme caution, as 36 | % this breaks the intended class representation. 37 | 38 | properties(SetAccess=protected) 39 | centers 40 | radii 41 | end 42 | 43 | properties(Dependent) 44 | bounded 45 | circles 46 | end 47 | 48 | methods 49 | function R = circleRegion(varargin) 50 | args = {}; 51 | if nargin 52 | if ischar(varargin{nargin}) 53 | clist = varargin(1:nargin-1); 54 | varargin = varargin(nargin); 55 | else 56 | clist = varargin; 57 | varargin = {}; 58 | end 59 | if numel(clist) == 1 && isa(clist{1}, 'cell') 60 | clist = clist{1}; 61 | end 62 | 63 | if ~isempty(clist) 64 | if any(cellfun(@(x) ~isa(x, 'circle'), clist)) 65 | error('CMT:InvalidArgument', ... 66 | 'Expected one or more circles.') 67 | end 68 | 69 | m = numel(clist); 70 | cv(m,1) = 1i; 71 | rv(m,1) = 0; 72 | for j = 1:m 73 | cv(j) = clist{j}.center; 74 | rv(j) = clist{j}.radius; 75 | end 76 | 77 | if numel(varargin) == 0 || ~strcmp(varargin{1}, 'noCheck') 78 | % Check bounded/unbounded/intersect. 79 | isinside = circleRegion.circleCheck(cv, rv); 80 | if numel(clist) > 1 && all(isinside) 81 | args = {clist{1}, clist(2:end)}; 82 | elseif numel(clist) == 1 || ~any(isinside) 83 | args = {clist, 'exteriorto'}; 84 | else 85 | error('CMT:InvalidArgument', ... 86 | ['The circles given do not define an expected region.' ... 87 | ' See help.']) 88 | end 89 | end 90 | end 91 | end 92 | 93 | R = R@region(args{:}); 94 | if ~nargin || isempty(clist) 95 | return 96 | end 97 | 98 | R.centers = cv; 99 | R.radii = rv; 100 | end 101 | 102 | function R = apply(R, M) 103 | % Apply Mobius map to circle region. 104 | 105 | if ~isa(M, 'mobius') 106 | error('CMT:NotDefined', ... 107 | 'Applying class %s to circleRegion is not defined.', ... 108 | class(M)) 109 | end 110 | 111 | C = boundary(R); 112 | if ~iscell(C) 113 | C = {C}; 114 | end 115 | for j = 1:numel(C) 116 | C{j} = M(C{j}); 117 | end 118 | R = circleRegion(C); 119 | end 120 | 121 | function R = inv(R) 122 | % Invert circle region; inner and outer boundaries change roles. 123 | C = boundary(R); 124 | if hasouter(R) 125 | R = region(C(2:end), C{1}); 126 | else 127 | R = region(C, 'interiorto'); 128 | end 129 | end 130 | 131 | function invfill(R, varargin) 132 | newplot 133 | washold = ishold; 134 | hold on 135 | 136 | if isbounded(R) 137 | fill(region(R.outerboundary{1}, 'exteriorto')) 138 | fill(region(R.innerboundary, 'interiorto')) 139 | else 140 | fill(inv(R)) 141 | end 142 | 143 | if ~washold 144 | hold off 145 | axis(plotbox(R)) 146 | aspectequal 147 | end 148 | end 149 | 150 | function tf = isbounded(R) 151 | tf = R.bounded; 152 | end 153 | 154 | function str = replicate(R) 155 | s = sprintf('%s = circleRegion({...\n', inputname(1)); 156 | m = numel(R.radii); 157 | for j = 1:m 158 | s = sprintf('%s circle(%s, %s)', s, ... 159 | num2str(R.centers(j), '%.6g'), num2str(R.radii(j), '%.6g')); 160 | if j < m 161 | s = sprintf('%s, ...\n', s); 162 | else 163 | s = sprintf('%s});\n', s); 164 | end 165 | end 166 | if nargout 167 | str = s; 168 | else 169 | fprintf('%s', s) 170 | end 171 | end 172 | 173 | %%%%% get/set %%%%% 174 | function b = get.bounded(R) 175 | b = hasouter(R); 176 | end 177 | 178 | function C = get.circles(R) 179 | C = boundary(R); 180 | end 181 | 182 | function R = set.bounded(R, b) 183 | if R.m ~= 1 184 | error('CMT:InvalidOperation', ... 185 | 'Bounded status may only be specified for the single circle case.') 186 | end 187 | if ~(islogical(b) && numel(b) == 1) 188 | error('CMT:InvalidArgument', ... 189 | 'Expected a single logical value.') 190 | end 191 | 192 | bstatus = R.bounded; 193 | if ~bstatus && b 194 | % Not bounded, but make it so. 195 | R.outerboundary = R.innerboundary; 196 | R.innerboundary = {}; 197 | elseif bstatus && ~b 198 | % Bounded, but make it not so. 199 | R.innerboundary = R.outerboundary; 200 | R.outerboundary = {}; 201 | end 202 | end 203 | end 204 | 205 | methods(Static) 206 | function isinside = circleCheck(cv, rv) 207 | m = numel(rv); 208 | isinside = false(m-1, 1); 209 | intersect = false; 210 | for i = 1:m 211 | for j = i+1:m 212 | sep = abs(cv(i) - cv(j)); 213 | if sep < rv(i) 214 | if rv(i) - sep <= rv(j) 215 | intersect = true; 216 | break 217 | elseif i == 1 218 | isinside(j-1) = true; 219 | end 220 | elseif sep > rv(i) 221 | if sep <= rv(i) + rv(j) 222 | intersect = true; 223 | break 224 | end 225 | else 226 | intersect = true; 227 | break 228 | end 229 | end 230 | if intersect 231 | error('CMT:InvalidArgument', ... 232 | 'Circle intersection detected.') 233 | end 234 | end 235 | end 236 | end 237 | 238 | end 239 | -------------------------------------------------------------------------------- /closedcurve.m: -------------------------------------------------------------------------------- 1 | classdef closedcurve 2 | % CLOSEDCURVE abstract base class for simple planar Jordan curves. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % (Re)written by Everett Kropf, 2014, 10 | % adapted from Toby Driscoll's code, originally 20??. 11 | 12 | properties 13 | cornerList % Placeholder. 14 | paramLength = 1 % Parameter length. 15 | end 16 | 17 | methods 18 | % Just use default constructor; nothing to do right now on construction. 19 | 20 | function box = boundbox(C) 21 | % Return bounding box for curve using evenly spaced points. 22 | t = C.paramLength*(0:199)'/200; 23 | box = cmt.boundbox(point(C, t)); 24 | end 25 | 26 | function varargout = corner(C, varargin) 27 | % I'm honestly not quite sure about the usage of this, it will probably 28 | % change drastically in the future. Haven't found it being used yet in any 29 | % of Toby's code examples. -- EK 30 | % Other than being set in the polygon constructor. -- EK 31 | v = C.cornerList; 32 | 33 | % Classes of input args? 34 | inclass = cellfun(@class, varargin, 'uniformoutput', false); 35 | 36 | % Did we get an index? 37 | j = find(strcmp('double', inclass)); 38 | if ~isempty(j) 39 | v = v(varargin{j}); 40 | end 41 | 42 | % Field selection? 43 | j = find(strcmp('char', inclass)); 44 | if ~isempty(j) 45 | varargout{1} = cat(1, v.(varargin{j})); 46 | else 47 | if nargout == 3 48 | t = cat(1, v.param); 49 | z = cat(1, v.point); 50 | alpha = cat(1, v.alpha); 51 | varargout = {t, z, alpha}; 52 | else 53 | varargout{1} = v; 54 | end 55 | end 56 | end 57 | 58 | function C = ctranspose(C) 59 | C = cinvcurve(C); 60 | end 61 | 62 | function display(C) 63 | fprintf('\n%s =\n', inputname(1)) 64 | disp(C) 65 | end 66 | 67 | function R = exterior(C) 68 | % EXTERIOR creates an exterior region bounded by curve. 69 | % 70 | % R = exterior(C) 71 | % Creates exterior region R bounded by closed curve C. 72 | % 73 | % See also closedcurve, region. 74 | 75 | if ~isa(C, 'closedcurve') 76 | error('CMT:InvalidArgument', ... 77 | 'Function argument must be a closed curve.') 78 | end 79 | 80 | R = region(C, 'exteriorto'); 81 | end 82 | 83 | function out = fill(C, varargin) 84 | washold = ishold; 85 | 86 | % Rely on adaptplot or overloaded plot to get curve "right". 87 | hold on 88 | h = plot(C); 89 | z = complex(get(h, 'xdata'), get(h, 'ydata')); 90 | delete(h) 91 | 92 | args = cmtplot.fillargs; 93 | h = fill(real(z), imag(z), args{:}, varargin{:}); 94 | 95 | if ~washold 96 | hold off 97 | end 98 | 99 | if nargout 100 | out = h; 101 | end 102 | end 103 | 104 | function R = interior(C) 105 | % INTERIOR creates a bounded region with boundary C. 106 | % 107 | % R = exterior(C) 108 | % Creates interior region R bounded by closed curve C. 109 | % 110 | % See also closedcurve, region. 111 | 112 | if ~isa(C, 'closedcurve') 113 | error('CMT:InvalidArgument', ... 114 | 'Function argument must be a closed curve.') 115 | end 116 | 117 | R = region(C); 118 | end 119 | 120 | function n = length(C) 121 | % Curve parameter length. 122 | n = C.paramLength; 123 | end 124 | 125 | function t = modparam(C, t) 126 | % Ensures parameter satisfies 0 <= t < C.paramLength. 127 | if any(t < 0 | 1 <= C.paramLength) 128 | t = mod(t, C.paramLength); 129 | end 130 | end 131 | 132 | function out = plot(C, varargin) 133 | % Plot curve in the plane. 134 | washold = ishold; 135 | newplot 136 | 137 | h = plotCurve(C); 138 | [cargs, pargs] = cmtplot.closedcurveArgs(varargin{:}); 139 | set(h, pargs{:}, cargs{:}); 140 | 141 | if ~washold 142 | axis(plotbox(C, 1.1)); 143 | set(gca, 'dataaspectratio', [1 1 1]) 144 | hold off 145 | end 146 | 147 | if nargout > 0 148 | out = h; 149 | end 150 | end 151 | 152 | function box = plotbox(C, scale) 153 | % Return plot box for curve using evenly spaced points. 154 | % 155 | % See also plotbox. 156 | 157 | if nargin < 2 158 | scale = []; 159 | end 160 | t = C.paramLength*(0:199)'/200; 161 | box = cmt.plotbox(point(C, t), scale); 162 | end 163 | 164 | function out = rsplot(C, varargin) 165 | % Plot curve on the Riemann sphere. 166 | washold = ishold; 167 | 168 | % Draw Riemann shpere if not there. 169 | if isempty(findobj(gca, 'tag','CMT:RiemannSphere')) || ~washold 170 | [xs, ys, zs] = sphere(36); 171 | mesh(0.995*xs, 0.995*ys, 0.995*zs, 'edgecolor', .85*[1 1 1], ... 172 | 'tag', 'CMT:RiemannSphere') 173 | hold on 174 | end 175 | 176 | % Draw on the sphere. 177 | function x = rspoint(t) 178 | z = point(C, t); 179 | [x1, x2, x3] = c2rs(z); 180 | x = [x1, x2, x3]; 181 | end 182 | h = adaptplot(@rspoint, [0, C.paramLength]); 183 | set(h, varargin{:}); 184 | 185 | if ~washold 186 | hold off 187 | end 188 | axis equal 189 | 190 | if nargout > 0 191 | out = h; 192 | end 193 | end 194 | 195 | function z = subsref(C, S) 196 | % Equate C(t) with point(C, t). Fallback to builtin subsref otherwise. 197 | if numel(S) == 1 && strcmp(S.type, '()') 198 | if numel(S.subs) == 1 199 | z = point(C, S.subs{1}); 200 | return 201 | else 202 | error('Object only takes single parenthised subscript.') 203 | end 204 | end 205 | 206 | z = builtin('subsref', C, S); 207 | end 208 | 209 | function xy = xypoint(C, t) 210 | z = point(C, t); 211 | xy = [real(z), imag(z)]; 212 | end 213 | end 214 | 215 | methods(Abstract=true) 216 | z = point(C, t) 217 | z = tangent(C, t) 218 | end 219 | 220 | methods(Hidden) 221 | function h = plotCurve(C, varargin) 222 | h = adaptplot(@(t) xypoint(C, t), [0, C.paramLength]); 223 | end 224 | end 225 | 226 | methods (Access=private) 227 | function [x1, x2, x3] = c2rs(z) 228 | % C2RS Cartesian complex coordinate to Riemann sphere projection. 229 | 230 | % This file is a part of the CMToolbox. 231 | % It is licensed under the BSD 3-clause license. 232 | % (See LICENSE.) 233 | 234 | % Copyright Toby Driscoll, 2014. 235 | 236 | theta = angle(z); 237 | absz = abs(z); 238 | phi = atan2(absz.^2 - 1, 2*abs(z)); 239 | phi(isinf(z)) = pi/2; 240 | [x1, x2, x3] = sph2cart(theta, phi, ones(size(theta))); 241 | 242 | end 243 | end 244 | 245 | end 246 | -------------------------------------------------------------------------------- /cmtobject.m: -------------------------------------------------------------------------------- 1 | classdef cmtobject 2 | % CMTOBJECT provides some base CMT functionality for classes. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | 10 | methods 11 | function prefs = get(this, property) 12 | % Retrieve preference values for module (class) stored in app data 13 | % facility. 14 | 15 | prefs = getappdata(0, 'cmt_prefs'); 16 | module = class(this); 17 | 18 | if isempty(prefs) || ~isfield(prefs, module) 19 | % Set defaults. 20 | if isa(property, 'optset') 21 | set(this, property); 22 | prefs = getappdata(0, 'cmt_prefs'); 23 | else 24 | error('CMT:NotDefined', ... 25 | 'No default values given to set for class %s.', ... 26 | module) 27 | end 28 | end 29 | 30 | if isfield(prefs, module) 31 | prefs = prefs.(module); 32 | else 33 | % This shouldn't actually happen. 34 | error('CMT:NotDefined', ... 35 | 'Unable to retreive app data for class %s.', ... 36 | module) 37 | end 38 | 39 | if nargin > 1 && ischar(property) 40 | if isprop(prefs, property) 41 | prefs = prefs.(property); 42 | else 43 | error('CMT:NotDefined', ... 44 | 'Property %s not defined for class %s%.', ... 45 | property, module) 46 | end 47 | end 48 | end 49 | 50 | function set(this, varargin) 51 | % Store preferences for module (class) in app data facility. 52 | 53 | prefs = getappdata(0, 'cmt_prefs'); 54 | module = class(this); 55 | 56 | if numel(varargin) == 1 57 | if isa(varargin{1}, 'optset') 58 | % Set default. 59 | prefs.(module) = varargin{1}; 60 | varargin = {}; 61 | else 62 | error('CMT:InvalidArgument', ... 63 | 'Expected an optset object.') 64 | end 65 | end 66 | 67 | prefs.(module) = set(prefs.(module), varargin{:}); 68 | setappdata(0, 'cmt_prefs', prefs); 69 | end 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /cmtplot.m: -------------------------------------------------------------------------------- 1 | classdef cmtplot < cmtobject 2 | % CMTPLOT collects common CMT plot tasks. 3 | % 4 | % This is probably more convoluted than it needs to be. 5 | 6 | % This file is a part of the CMToolbox. 7 | % It is licensed under the BSD 3-clause license. 8 | % (See LICENSE.) 9 | 10 | % Copyright Toby Driscoll, 2014. 11 | % Written by Everett Kropf, 2014. 12 | 13 | properties(Constant) 14 | % colors 15 | black = 'k' 16 | grey = 0.75*[1, 1, 1] 17 | none = 'none' 18 | end 19 | 20 | methods 21 | function p = cmtplot 22 | % This is here to allow get/set functionality. 23 | 24 | get(p, plotset); 25 | end 26 | end 27 | 28 | methods(Static) 29 | function [args, exargs] = closedcurveArgs(varargin) 30 | opts = get(cmtplot); 31 | if nargout > 1 32 | [opts, exargs] = set(opts, varargin{:}); 33 | else 34 | opts = set(opts, varargin{:}); 35 | end 36 | args = { ... 37 | 'color', opts.lineColor, ... 38 | 'linewidth', opts.lineWidth ... 39 | }; 40 | if ~cmtplot.hasNewGraphics 41 | args(5:6) = {'linesmoothing', opts.lineSmoothing}; 42 | end 43 | end 44 | 45 | function args = fillargs() 46 | args = {cmtplot.fillcolor, 'edgecolor', cmtplot.filledgecolor}; 47 | end 48 | 49 | function c = fillcolor() 50 | c = cmtplot.grey; 51 | end 52 | 53 | function c = filledgecolor() 54 | c = cmtplot.none; 55 | end 56 | 57 | function [args, exargs] = gridArgs(varargin) 58 | opts = get(cmtplot); 59 | if nargout < 2 60 | opts = set(opts, varargin{:}); 61 | else 62 | [opts, exargs] = set(opts, varargin{:}); 63 | end 64 | args = { 65 | 'color', opts.gridColor, ... 66 | 'linewidth', opts.lineWidth ... 67 | }; 68 | if ~cmtplot.hasNewGraphics 69 | args(5:6) = {'linesmoothing', opts.lineSmoothing}; 70 | end 71 | end 72 | 73 | function tf = hasNewGraphics() 74 | tf = ~verLessThan('matlab', '8.4'); 75 | end 76 | 77 | function tf = isFigHandle(val) 78 | if exist('isgraphics', 'builtin') == 5 79 | tf = isgraphics(val, 'figure'); 80 | else 81 | % Revert to older way. 82 | if ishghandle(tmp) 83 | tf = strcmp(get(tmp, 'type'), 'figure'); 84 | else 85 | tf = false; 86 | end 87 | end 88 | end 89 | 90 | function whitefigure(fig) 91 | if ~nargin 92 | fig = gcf; 93 | end 94 | set(fig, 'color', 'white'); 95 | end 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /conformalmap.m: -------------------------------------------------------------------------------- 1 | classdef conformalmap < cmtobject 2 | % CONFORMALMAP base class. 3 | % 4 | % This class has two purposes. As a base class, which would normally be 5 | % abstract, but we need its constructor to work for the second purpose, 6 | % which is to act as an automated map creator. 7 | % 8 | % For example, with certain combinations of domains and ranges, 9 | % f = conformalmap(domain, range) 10 | % will be able to select the correct method to create a conformal map, 11 | % e.g., 12 | % f = conformalmap(unitcircle, polygon). 13 | % This second functionality has yet to be added. 14 | 15 | % This file is a part of the CMToolbox. 16 | % It is licensed under the BSD 3-clause license. 17 | % (See LICENSE.) 18 | 19 | % Copyright Toby Driscoll, 2014. 20 | % Written by Everett Kropf, 2014. 21 | 22 | properties 23 | theDomain % Region object. 24 | theRange % Region object. 25 | functionList 26 | end 27 | 28 | methods 29 | function f = conformalmap(domain, range, varargin) 30 | if ~nargin 31 | return 32 | end 33 | 34 | argsok = false; 35 | composition = false; 36 | anonymous = false; 37 | if nargin >= 2 38 | if all(cellfun(@(c) isa(c, 'conformalmap'), ... 39 | [{domain, range}, varargin])) 40 | composition = true; 41 | argsok = true; 42 | else 43 | argsok = (isempty(domain) | isa(domain, 'region')) ... 44 | & (isempty(range) | isa(range, 'region')); 45 | if nargin == 3 46 | anonymous = isa(varargin{1}, 'function_handle'); 47 | argsok = argsok & anonymous; 48 | elseif nargin > 3 49 | argsok = false; 50 | end 51 | end 52 | end 53 | if ~argsok 54 | error('CMT:InvalidArgument', ... 55 | 'Expected domain and range region objects.') 56 | end 57 | 58 | if composition 59 | f.functionList = [{domain, range}, varargin]; 60 | f.theDomain = f.functionList{1}.domain; 61 | f.theRange = f.functionList{end}.range; 62 | else 63 | f.theDomain = domain; 64 | f.theRange = range; 65 | if anonymous 66 | f.functionList = varargin; 67 | end 68 | end 69 | end 70 | 71 | function w = apply(f, z) 72 | % w = apply(f, z) 73 | % Apply the conformal map to z. 74 | % 75 | % See the apply concept in the developer docs for more details. 76 | 77 | if iscomposition(f) 78 | % f is a composition, apply each map in turn. 79 | w = z; 80 | for k = 1:numel(f.functionList) 81 | w = apply(f.functionList{k}, w); 82 | end 83 | return 84 | end 85 | 86 | % Try asking the target object first. 87 | try 88 | w = apply(z, f); 89 | return 90 | catch err 91 | if ~any(strcmp(err.identifier, ... 92 | {'MATLAB:UndefinedFunction', 'CMT:NotDefined'})) 93 | rethrow(err) 94 | end 95 | end 96 | 97 | % Try the apply defined by subclass. 98 | try 99 | if isa(z, 'gridcurves') 100 | w = cell(numel(z), 1); 101 | for k = 1:numel(w) 102 | w{k} = applyMap(f, z{k}); 103 | end 104 | w = gridcurves(w); 105 | else 106 | w = applyMap(f, z); 107 | end 108 | catch err 109 | if strcmp(err.identifier, 'MATLAB:UndefinedFunction') 110 | msg = sprintf('Applying %s to %s is not defined.', class(f), class(z)); 111 | if ~isempty(err.message) 112 | msg = sprintf('%s\n%s', msg, err.message); 113 | end 114 | error('CMT:NotDefined', msg) 115 | else 116 | rethrow(err) 117 | end 118 | end 119 | end 120 | 121 | function disp(f) 122 | fprintf('** conformalmap object **\n\n') 123 | if ~isempty(f.theDomain) 124 | fprintf('---\nhas domain\n') 125 | disp(f.theDomain) 126 | end 127 | if ~isempty(f.theRange) 128 | fprintf('---\nhas range\n') 129 | disp(f.theRange) 130 | end 131 | 132 | if iscomposition(f) 133 | fprintf('---\nthis map is a composition of:\n(in order of application)\n') 134 | for k = 1:numel(f.functionList) 135 | disp(f.functionList{k}) 136 | end 137 | end 138 | end 139 | 140 | function d = domain(f) 141 | % Return map domain region. 142 | d = f.theDomain; 143 | end 144 | 145 | function tf = isanonymous(f) 146 | tf = numel(f.functionList) == 1 ... 147 | && isa(f.functionList{1}, 'function_handle'); 148 | end 149 | 150 | function tf = iscomposition(f) 151 | tf = numel(f.functionList) > 1; 152 | end 153 | 154 | function out = plot(f, varargin) 155 | washold = ishold; 156 | 157 | if isempty(f.theRange) || ... 158 | ~(isempty(f.theDomain) || hasgrid(f.theDomain)) 159 | warning('Map range not set or domain has an empty grid. No plot produced.') 160 | return 161 | end 162 | 163 | cah = newplot; 164 | hold on 165 | 166 | % Separate grid construction and plot 'name'/value pairs. 167 | [gargs, pargs] = separateArgs(get(f.theDomain), varargin{:}); 168 | hg = plot(apply(f, grid(f.theDomain, gargs{:})), pargs{:}); 169 | hb = plot(f.theRange, pargs{:}); 170 | 171 | if ~washold 172 | cmtplot.whitefigure(get(cah, 'parent')) 173 | axis(plotbox(f.theRange)) 174 | aspectequal 175 | if cmtplot.hasNewGraphics 176 | % Bug in new graphics won't show smoothed lines if this 177 | % isn't done when drawing on a figure created by the plot 178 | % call. Just need some way to force a redraw after 179 | % the lines are put up. 180 | drawnow 181 | end 182 | axis off 183 | hold off 184 | end 185 | 186 | if nargout 187 | out = [hg; hb]; 188 | end 189 | end 190 | 191 | function r = range(f) 192 | % Return map range region. 193 | r = f.theRange; 194 | end 195 | 196 | function varargout = subsref(f, S) 197 | if numel(S) == 1 && strcmp(S.type, '()') 198 | [varargout{1:nargout}] = apply(f, S.subs{:}); 199 | else 200 | [varargout{1:nargout}] = builtin('subsref', f, S); 201 | end 202 | end 203 | end 204 | 205 | methods 206 | % Arithmetic operators. 207 | function f = mtimes(f1, f2) 208 | % Interpret f1*f2 as the composition f1(f2(z)), or as scalar times 209 | % the image. 210 | 211 | % Make sure a conformalmap is first. 212 | if isnumeric(f1) 213 | tmp = f1; f1 = f2; f2 = tmp; 214 | end 215 | 216 | if isnumeric(f2) 217 | const = f2; % for naming 218 | f = conformalmap(f1,@(z) const*z); 219 | elseif isa(f2,'conformalmap') 220 | % Do a composition of the two maps. 221 | f = conformalmap(f2, f1); 222 | else 223 | error('CMT:conformalmap:mtimes',... 224 | 'Must either multiply by a scalar or compose two maps.') 225 | end 226 | end 227 | 228 | function g = minus(f,c) 229 | if isnumeric(f) 230 | g = plus(-f,c); 231 | else 232 | g = plus(f,-c); 233 | end 234 | end 235 | 236 | function g = plus(f,c) 237 | % Defines adding a constant (translation of the range). 238 | 239 | if isnumeric(f) 240 | tmp = c; c = f; f = tmp; 241 | end 242 | 243 | if ~isnumeric(c) || (length(c) > 1) 244 | error('CMT:conformalmap:plus',... 245 | 'You may only add a scalar to a conformalmap.') 246 | end 247 | 248 | g = conformalmap( f, @(z) z+c ); 249 | end 250 | 251 | function g = mpower(f,n) 252 | 253 | if (n ~= round(n)) || (n < 0) 254 | error('CMT:conformalmap:integerpower',... 255 | 'Power must be a positive integer.') 256 | end 257 | 258 | g = f; 259 | p = floor(log2(n)); 260 | for k = 1:p 261 | g = conformalmap(g,g); 262 | end 263 | for k = n-p 264 | g = conformalmap(g,f); 265 | end 266 | end 267 | 268 | function g = uminus(f) 269 | g = conformalmap( f, @(z) -z ); 270 | end 271 | end 272 | 273 | methods(Access=protected) 274 | function w = applyMap(f, z) 275 | if isanonymous(f) 276 | w = f.functionList{1}(z); 277 | else 278 | % Default map is identity. 279 | if ~isa(class(f), 'conformalmap') 280 | warning('CMT:BadThings', ... 281 | ['Identity map used when conformalmap is subclassed.\n' ... 282 | '(Define applyMap in subclass %s?)'], class(f)) 283 | end 284 | w = z; 285 | end 286 | end 287 | end 288 | 289 | end 290 | -------------------------------------------------------------------------------- /corner.m: -------------------------------------------------------------------------------- 1 | classdef corner 2 | % CORNER class represents corners for closed curves. 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | properties 12 | param 13 | point 14 | alpha 15 | end 16 | 17 | properties(Access=private) 18 | % Should probably use metadata. 19 | prop_list = {'param', 'point', 'alpha'} 20 | end 21 | 22 | methods 23 | function C = corner(cinfo) 24 | if ~nargin 25 | return 26 | end 27 | 28 | if isa(cinfo, 'corner') 29 | C = cinfo; 30 | return 31 | end 32 | 33 | % Allow cell and structure forms for construction. 34 | if isa(cinfo, 'struct') 35 | if ~all(isfield(cinfo, C.prop_list)) 36 | error('Corner information was supplied incorrectly.') 37 | end 38 | for prop = C.prop_list 39 | C.(prop) = cinfo.(prop); 40 | end 41 | elseif isa(cinfo, 'cell') 42 | % Assume order of cell entries. 43 | for k = 1:numel(C.prop_list) 44 | C.(C.prop_list{k}) = num2cell(cinfo{k}); 45 | end 46 | else 47 | end 48 | end 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /disk.m: -------------------------------------------------------------------------------- 1 | classdef disk < region 2 | % DISK is a region bounded by a circle. 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % (Re)written by Everett Kropf, 2014, 10 | % adapted from an idea by Toby Driscoll, 20??. 11 | 12 | methods 13 | function D = disk(center, radius) 14 | badargs = false; 15 | switch nargin 16 | case 0 17 | C = []; 18 | 19 | case 1 20 | if isa(center, 'disk') 21 | C = center.outerboundary; 22 | elseif isa(center, 'double') && numel(center) == 3 23 | C = circle(center); 24 | elseif isa(center, 'circle') && ~isinf(center) 25 | C = center; 26 | else 27 | badargs = true; 28 | end 29 | 30 | case 2 31 | if isa(center, 'double') && isa(radius, 'double') ... 32 | && numel(center) == 1 && numel(radius) == 1 33 | C = circle(center, radius); 34 | else 35 | badargs = true; 36 | end 37 | 38 | otherwise 39 | badargs = true; 40 | end 41 | if badargs 42 | error('CMT:InvalidArgument', ... 43 | 'Expected 3 points or a center and radius.') 44 | end 45 | 46 | if isempty(C) 47 | supargs = {}; 48 | else 49 | supargs = {C}; 50 | end 51 | 52 | D = D@region(supargs{:}); 53 | get(D, gridset); 54 | end 55 | 56 | function gd = carlesonGrid(D, opts) 57 | % Generate a basic Carleson grid. Default 5 levels. 58 | 59 | levels = opts.numLevels; 60 | 61 | nu = 32; % Base radial line number. 62 | r = 0.6; % Base circle radius. 63 | 64 | gc = cell(1 + levels + 2^(levels-1)*nu, 1); 65 | 66 | % Level 0 circle. 67 | ncp = 200; 68 | gc{1} = r*exp(2i*pi*(0:ncp-1)'/(ncp-1)); 69 | 70 | % Base number of radial line points per unit length. 71 | ppul = 200; 72 | 73 | idx = 1; 74 | for j = 1:levels 75 | if j > 1 76 | nuj = 2^(j-2)*nu; 77 | else 78 | nuj = nu; 79 | end 80 | ncr = ceil(j*ppul*(1 - r)); 81 | rln = linspace(r, 1 - 1e-8, ncr)'; 82 | dt = 2*pi/nuj; 83 | off = (j > 1)*dt/2; 84 | for k = 1:nuj 85 | gc{idx + k} = rln*exp(1i*(off + (k-1)*dt)); 86 | end 87 | 88 | idx = idx + nuj + 1; 89 | r = (1 + r)/2; 90 | np = (j+1)*ncp; 91 | gc{idx} = r*exp(2i*pi*(0:np-1)'/(np-1)); 92 | end 93 | 94 | gd = gridcurves(gc); 95 | c = center(outer(D)); 96 | r = radius(outer(D)); 97 | if ~(c == 0 && r == 1) 98 | gd = c + r*gd; 99 | end 100 | end 101 | 102 | function gd = grid(D, varargin) 103 | opts = get(D); 104 | opts = set(opts, varargin{:}); 105 | 106 | switch opts.gridType 107 | case 'polar' 108 | gd = polarGrid(D, opts); 109 | 110 | case 'carleson' 111 | gd = carlesonGrid(D, opts); 112 | 113 | otherwise 114 | error('CMT:NotDefined', ... 115 | 'Grid type "%s" not recognized.', type) 116 | end 117 | end 118 | 119 | 120 | function tf = hasgrid(~) 121 | tf = true; 122 | end 123 | 124 | function gd = polarGrid(D, opts) 125 | nrad = opts.numRadialLines; 126 | ncirc = opts.numCircularLines; 127 | 128 | npt = 200; 129 | c = center(outer(D)); 130 | r = radius(outer(D)); 131 | 132 | curves = cell(nrad + ncirc, 1); 133 | zg = (1:npt)'/(npt+1); 134 | for k = 1:nrad 135 | curves{k} = c + r*exp(2i*pi*(k-1)/nrad)*zg; 136 | end 137 | zg = exp(2i*pi*(0:npt-1)'/(npt-1)); 138 | for k = 1:ncirc 139 | curves{nrad + k} = c + r*k/(ncirc+1)*zg; 140 | end 141 | 142 | gd = gridcurves(curves); 143 | end 144 | end 145 | 146 | end 147 | -------------------------------------------------------------------------------- /diskex.m: -------------------------------------------------------------------------------- 1 | classdef diskex < region 2 | % DISKEX represents a region exterior to a circle. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | properties 12 | extent = 2 % Default grid extent. 13 | end 14 | 15 | methods 16 | function D = diskex(center, radius) 17 | badargs = false; 18 | switch nargin 19 | case 0 20 | C = []; 21 | 22 | case 1 23 | if isa(center, 'disk') || isa(center, 'diskex') 24 | C = center.outerboundary; 25 | elseif isa(center, 'double') && numel(center) == 3 26 | C = circle(center); 27 | elseif isa(center, 'circle') && ~isinf(center) 28 | C = center; 29 | else 30 | badargs = true; 31 | end 32 | 33 | case 2 34 | if isa(center, 'double') && isa(radius, 'double') ... 35 | && numel(center) == 1 && numel(radius) == 1 36 | C = circle(center, radius); 37 | else 38 | badargs = true; 39 | end 40 | 41 | otherwise 42 | badargs = true; 43 | end 44 | if badargs 45 | error('CMT:InvalidArgument', ... 46 | 'Expected 3 points or a center and radius.') 47 | end 48 | 49 | if isempty(C) 50 | supargs = {}; 51 | else 52 | supargs = {C, 'exteriorto'}; 53 | end 54 | 55 | D = D@region(supargs{:}); 56 | get(D, gridset); 57 | end 58 | 59 | function gd = carlesonGrid(D, opts) 60 | gd = carlesonGrid(disk(0, 1), opts); 61 | 62 | c = center(inner(D)); 63 | r = radius(inner(D)); 64 | gd = c + r/gd; 65 | end 66 | 67 | function gd = grid(D, varargin) 68 | opts = get(D); 69 | opts = set(opts, varargin{:}); 70 | 71 | switch opts.gridType 72 | case 'polar' 73 | gd = polarGrid(D, opts); 74 | 75 | case 'carleson' 76 | gd = carlesonGrid(D, opts); 77 | 78 | otherwise 79 | error('CMT:NotDefined', ... 80 | 'Grid type "%s" not recognized.', type) 81 | end 82 | end 83 | 84 | function tf = hasgrid(~) 85 | tf = true; 86 | end 87 | 88 | function gd = polarGrid(D, opts) 89 | nrad = opts.numRadialLines; 90 | ncirc = opts.numCircularLines; 91 | 92 | npt = 200; 93 | c = center(inner(D)); 94 | r = radius(inner(D)); 95 | 96 | % Grid extent radius extension. 97 | re = D.extent*r - r; 98 | 99 | curves = cell(nrad + ncirc, 1); 100 | zg = re*(1:npt)'/(npt+1); 101 | for k = 1:nrad 102 | curves{k} = c + exp(2i*pi*(k-1)/nrad)*(r + zg); 103 | end 104 | zg = exp(2i*pi*(0:npt-1)'/(npt-1)); 105 | for k = 1:ncirc 106 | curves{nrad + k} = c + (r + re*k/(ncirc + 1))*zg; 107 | end 108 | 109 | gd = gridcurves(curves); 110 | end 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /doc/dev_guide.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{fullpage} 3 | \usepackage{listings} 4 | \usepackage[]{color} 5 | \usepackage[]{url} 6 | \usepackage[]{hyperref} 7 | 8 | \definecolor{gray}{rgb}{0.5,0.5,0.5} 9 | \definecolor{dkgreen}{rgb}{.068,.578,.068} 10 | \definecolor{dkpurple}{rgb}{.320,.064,.680} 11 | 12 | % Matlab style, stolen from Chebfun coding style guide. 13 | \lstset{ 14 | language=Matlab, 15 | keywords={break,case,catch,continue,else,elseif,end,for,function, 16 | global,if,otherwise,persistent,return,switch,try,while}, 17 | basicstyle=\small\ttfamily, 18 | keywordstyle=\color{blue}\bfseries, 19 | commentstyle=\color{dkgreen}, 20 | stringstyle=\color{dkpurple}, 21 | backgroundcolor=\color{white}, 22 | tabsize=4, 23 | showspaces=false, 24 | showstringspaces=false 25 | } 26 | 27 | \title{Conformal Mapping Toolkit Developer's Guide} 28 | \author{E. Kropf and T. Driscoll} 29 | \date{\today} 30 | 31 | \begin{document} 32 | 33 | \maketitle 34 | \tableofcontents 35 | \clearpage 36 | 37 | \section{Introduction} 38 | This guide describes design decisions behind the Conformal Mapping Toolbox (CMToolbox, CMT). It is written in hopes that it will avoid confusion or clashes in later development. There is a version of this software written in the older class style of MATLAB; this document covers the code converted to the new MATLAB syntax. 39 | 40 | \subsection{Language choice} 41 | The initial version of this software is developed in MATLAB. It is hoped that it will be ported to other frameworks in the future. 42 | 43 | Why MATLAB? Conformal mapping is a calculation game, and MATLAB is a super expensive, glorified calculator. Those generally interested in conformal mapping live in math(s) departments, and thus generally have access to MATLAB provided, so the cost is an artificial barrier for the target audience. 44 | 45 | Why other frameworks? MATLAB is a frustrating place to write software, and it's demonstrably slower in some unavoidable circumstances. 46 | 47 | \section{Code rules} 48 | Rules listed here are more like strong suggestions. To make things uniform, try to follow them where possible, but \emph{if it makes more sense to do something else}, then by all means do that. The main goal is readability, followed closely by maintainability (ease of diff by the VCS) [and by ease of use?]. Some of what follows is taken from or influenced by \cite{chebfunCodingStyle} and \cite{pythonStyleGuide}. Some of this is made up as needed. 49 | 50 | CMT will use only classes defined using MATLAB's newer \verb+classdef+ keyword. The old class system, although still in use, is too limited in flexibility. 51 | 52 | Each new, non-abstract class defined will also have a test class written to be placed in the \verb+test+ directory. These tests will be called by the \verb+run_tests+ function as a way of checking code functionality. If a developer makes a change to the existing code, \verb+run_tests+ is a way to ensure the system hasn't been broken by the change. 53 | 54 | Unless there is a good reason, default to using public properties. A proper property naming scheme is still under discussion. % Class properties will be generally accessed via ``get'' type functions. To make this easier, follow property names with an underscore, and the ``get'' function will use the property name without the underscore. For example the polygon class will have an \verb+angle_+ property, and a method \verb+angle+ by which to retrieve that property. 55 | 56 | \subsection{General layout} 57 | [TODO: Comments needed regarding multiline expressions.] 58 | 59 | \begin{description} 60 | \item[Indentation] Each level of indentation is given by 4 spaces -- no tab characters. Default indentation behaviour by the MATLAB editor should suffice, with one exception. The \verb|method| and \verb|property| blocks within a \verb|classdef| should be flush with the left margin (it is an extreme waste of space otherwise). 61 | \item[Line width.] Lines should be less than 80 characters. 62 | \item[Command separation.] Giving multiple commands on one line should be avoided, as should placing an \verb|if| block or loop on one line. 63 | \item[Assignemnt and logical operators.] These should be surrounded by 1 space. 64 | \item[Comma and semicolon.] When indexing an array, no space will be used on either side of a comma or semicolon. When separating function arguments, there should be one space following each comma. 65 | \begin{lstlisting}[frame=single] 66 | A(j,k) = C{k,j}; % Array indexing. 67 | someFunction(arg1, arg2); % Function call. 68 | \end{lstlisting} 69 | When creating arrays, column entries should be separated by a comma (followed by a space). Semicolons used when creating arrays should also be followed by a space. 70 | \item[Parenthesis and brackets.] An open parenthesis or bracket (brace) character, whether starting a function call or defining an array, should not be followed by a space. When a parenthesis is used in a function call for the argument list, there should be no space between the function name and the parenthesis. A closing brace or parenthesis must not be preceded by a space. 71 | \item[Binary arithmetic operators.] The binary operators \verb|+| and \verb|-| should be surrounded on either side by a space character. The operators \verb|*| and \verb|/| (and their dotted equivalents) should not be surrounded by a space. 72 | \end{description} 73 | 74 | \subsection{Help text} 75 | Every function and class definition file should have help text. At a minimum a function should document its inputs and outputs. The class file should at a minimum describe the class, the constructor arguments, and major properties and methods. 76 | 77 | \subsection{Naming conventions} 78 | 79 | A corrupt version of camel case will be used to name functions, classes, properties and methods. Unless otherwise stated, all camel case names will start with a lowercase letter. This will probably never be otherwise stated. 80 | 81 | In contrast to the statement in the previous paragraph, major classes and functions, \textit{i.e.}, things called from the command line, will be defined using all lowercase letters and should be as short is practically possible. This is an attempt to follow the style of MATLAB command line usage with which most people are familiar. This also keeps typing at the command line to a minimum, although this justification is probably questionable. For and example consider the simply-connected Szeg\"o map. Instead of \verb+szegoMap+, which follows the camel case convention, we use \verb+szmap+, which is easier to type. (Is it easier to remember?) 82 | 83 | \section{Concepts} 84 | \subsection{Apply} 85 | Let $A$ and $B$ be objects (instantiations of classes). We say ``\emph{apply $A$ to $B$}'' to mean that an object $A$ acts on an object $B$ in a way to be defined by the relationship between the two objects. This should be implemented as follows: 86 | \begin{itemize} 87 | \item Object $A$ should ask object $B$ if $B$ has a preferred way of having $A$ act on it. 88 | \item If $B$ has no preference, then $A$ should attempt apply itself in a generic fashion specific to $A$. 89 | \item Failing these, there should be an ``$A$ applied to $B$ is not defined'' error. 90 | \end{itemize} 91 | 92 | \noindent In MATLAB: \verb+apply(A, B)+ or \verb+A*B+. For example, 93 | \begin{lstlisting}[frame=single] 94 | function C = apply(A, B) 95 | % C = apply(A, B) 96 | % Apply object A to object B for result C. 97 | % 98 | % See the apply concept in the developer docs for more details. 99 | 100 | % Try asking the target object first. 101 | try 102 | C = apply(B, A); 103 | return 104 | catch err 105 | if ~any(strcmp(err.identifier, ... 106 | {'MATLAB:UndefinedFunction', 'CMT:NotDefined'})) 107 | rethrow(err) 108 | end 109 | end 110 | 111 | % Try the default/general apply. 112 | try 113 | C = applyDefault(A, B); 114 | catch err 115 | if strcmp(err.identifier, 'MATLAB:UndefinedFunction') 116 | msg = sprintf('Applying %s to %s is not defined.', ... 117 | class(f), class(z)); 118 | if ~isempty(err.message) 119 | msg = sprintf('%s\n%s', msg, err.message); 120 | end 121 | error('CMT:NotDefined', msg) 122 | else 123 | rethrow(err) 124 | end 125 | end 126 | end 127 | \end{lstlisting} 128 | % \begin{itemize} 129 | % \item Object $A$ should try \verb+apply(B, A)+. 130 | % \item If $B$ doesn't know about this, then either 131 | % \begin{itemize} 132 | % \item $B$ hasn't defined an \verb+apply()+ method, which may be checked by \verb+ismethod+ before trying, or 133 | % \item $B$ has defined \verb+apply()+, but doesn't know what to do with $A$ and errors with \verb+CMT:NotDefined+. 134 | % \end{itemize} 135 | % \item Failing this, object $A$ should then call the protected function \verb+apply_map(A, B)+. 136 | % \item If object $A$ fails to apply itself to $B$ it errors with \verb+CMT:NotDefined+. 137 | % \end{itemize} 138 | 139 | Having object $A$ ask object $B$ first allows target objects to specialize map applications. (I swear I had something in mind as an example for this when I thought it up, but now \ldots -- EK.) See the \verb+conformalmap+ class for an implemented example of this concept. 140 | 141 | 142 | \section{Class structure} 143 | Classes listed here are in general base classes, meant to be sub-classed. Some, like the \verb+homog+ class are there to provide functionality for other classes. Properties and methods listed below are not a complete list, and are mainly those that define the `idea' of the class. Implementation details can be found in the code. 144 | 145 | \subsection{Homogeneous numbers} 146 | Class \verb+homog+. 147 | 148 | Originally designed by Toby Driscoll, this class was implemented to handle divide by zero warnings generated by MATLAB. In addition the class provides a way to meaningfully apply an angle to a point at infinity. Recent versions of MATLAB have moved to a silent divide by zero, but having an angle for the point at infinity is still clearly useful. 149 | 150 | A homogeneous coordinate is defined by two scalar values, $z_1$ and $z_2$, such that $z = z_1/z_2$. Every seemingly non-homogeneous complex number $w$ can be assumed to have the homogeneous value $w/1$. Thus a point at infinity may be defined by $z_1/0$ with $\arg\{z_1/0\} := \arg\{z_1\}$. 151 | 152 | \paragraph{Has properties:} 153 | \begin{itemize} 154 | \item A numerator, $z_1$. 155 | \item A denominator, $z_2$. 156 | \end{itemize} 157 | 158 | \paragraph{Provides methods:} 159 | The key to this class is to behave in almost all cases exactly like the built in MATLAB \verb+double+ class. 160 | 161 | \subsection{Closed curves} 162 | Abstract class \verb+closedcurve+. 163 | 164 | Closed curves are simple (non-intersecting), oriented curves in the plane which define an ``inside'' and ``outside'' region. Each curve is parameterized, \textit{e.g.}, by some function $z(t)$ for $0\le t\le 1$ where $z(0) = z(1)$, such that the ``inside'' of the curve is to the left of the tangent vector given by this parameterization. 165 | 166 | \paragraph{Has properties:} [Not sure what properties a base closed curve should have. Initially thought there should be a \emph{length} here, so subclasses could choose an arbitrary parameterization length, but not sure what that gains us over just forcing everything to be parameterized on the unit interval. Non-polygon corner information is going to be useful at some point.] 167 | 168 | \paragraph{Provides methods:} 169 | \begin{itemize} 170 | \item \verb+point+ -- This is the abstract parameterization function $z(t)$ which expects subclasses to return a vector of points on the curve $z$ for an input vector of values $t$. 171 | \item \verb+tangent+ -- This is the abstract tangent function which expects subclasses to return a tangent vector $z'(t)$ for each $t$ given. It is also expected that calculating $z'(t)/|z'(t)|$ gives the unit tangent vector. 172 | \item \verb+plot+ -- Provides basic plotting logic that should be common to all closed curves. Calls the function \verb+plot_+ to actually plot the curve. Subclasses should override \verb+plot_+ to provide specific curve plotting instructions. 173 | \item \verb+fill+ -- Provide the basic logic to fill the inside of the closed curve; uses \verb+plot+ to do the work. 174 | \item \verb+plot_+ -- Simply try to use \verb+adaptplot+ with the \verb+point+ function to plot the curve. 175 | \end{itemize} 176 | 177 | \subsubsection{Generalized circle} 178 | Class \verb+circle < closedcurve+. 179 | 180 | A circle may be defined by a (finite) center and radius, or by 3 points on the boundary of the circle. In the latter case, if the points are collinear, then the circle is a line in the plane, and the circle goes through the north pole of the Riemann sphere. 181 | 182 | \paragraph{Uses classes:} The circle uses the \verb+standardmap+ from the M\"obius class to determine geometry when given 3 points. It might seem more sensible to construct a M\"obius object directly to calculate the transformation determined by $[0,1,\infty]\mapsto[z_1, z_2, z_3]$, but the M\"obius object constructs two circles as a domain and range (mainly for plotting purposes), so we have to avoid that bit of vicious circularity. 183 | 184 | \paragraph{Has properties:} 185 | \begin{itemize} 186 | \item A \emph{center}, which is \verb+NaN+ if the circle is infinite. 187 | \item A \emph{radius} in the interval $(0,\infty)$. 188 | \end{itemize} 189 | 190 | \paragraph{Provides methods:} 191 | \begin{itemize} 192 | \item \verb+plot_+ -- If the circle is finite, the superclass \verb+plot_+ is used. If not, the polygon class (as a line) is used, since its plot routine has truncation built in. 193 | \end{itemize} 194 | 195 | \subsection{Regions} 196 | Class \verb+region+. 197 | 198 | A region is defined by zero or more closed curves. Not sure at this point if we want a region with zero boundary elements to be an empty region, or if we want it to be the entire plane (plus the point at infinity). 199 | 200 | \paragraph{Uses classes:} Regions rely heavily on the closed curve classes. 201 | 202 | \paragraph{Has properties:} 203 | \begin{itemize} 204 | \item List of outer boundary closed curves. 205 | \item List of inner boundary closed curves. 206 | \end{itemize} 207 | 208 | A region with one outer boundary curve and no inner curves is a bounded, simply connected region. One inner boundary and no outer boundary is a simply connected unbounded region. One outer boundary and $n$ inner boundaries, provided the inner boundaries are disjoint and bounded by the outer, is a bounded, $n+1$ connected region. A region with $n$ disjoint inner boundaries is an unbounded, $n$ connected region. 209 | 210 | At present, it's completely up to the user to ensure the boundary combinations make any sense. Is there a way to enforce/check allowed boundary conditions? 211 | 212 | \paragraph{Provides methods:} 213 | \begin{itemize} 214 | \item \verb+plot+ -- Has logic to plot the boundary curves with proper fill. 215 | \item \verb+fill+ -- Has the logic to plot the proper (interior/exterior) fill for the boundary curves. 216 | \end{itemize} 217 | 218 | \subsubsection{Disk} 219 | Class \verb+disk+. 220 | 221 | Defines a region bounded by a finite circle. Currently \verb+unitdisk+ is a wrapper function to call disk with the proper arguments. Does it need its own class definition? 222 | 223 | \subsection{Conformal maps} 224 | Class \verb+conformalmap+. 225 | 226 | \paragraph{Has properties:} 227 | \begin{itemize} 228 | \item A \emph{domain} region object. 229 | \item A \emph{range} region object. 230 | \end{itemize} 231 | 232 | \paragraph{Provides methods:} 233 | \begin{itemize} 234 | \item \verb+apply+ -- Supplies the logic for the \emph{apply} concept, calls the (hopefully) subclassed \verb+apply_map+ method. 235 | \item \verb+plot+ -- Has the plot logic to draw the image under the map of a grid in the domain region, if it exists, on top of the filled region with a boundary. 236 | \end{itemize} 237 | 238 | \subsubsection{M\"obius} 239 | Class \verb+mobius < conformalmap+. 240 | 241 | On construction given two vectors of 3 points, this class creates two circle regions to use as a domain and range for plotting purposes. The map should work for any point in the plane. 242 | 243 | \paragraph{Provides methods:} 244 | \begin{itemize} 245 | \item \verb+apply_map+ -- Has logic for applying the M\"obius transformation to a circle or a point (or set of points) in the plane. 246 | \end{itemize} 247 | 248 | 249 | \begin{thebibliography}{99} 250 | \bibitem{chebfunCodingStyle} 251 | The Chebfun Team, 252 | ``Chebfun Coding Guide for Developers,'' 253 | Unpublished. 254 | 255 | \bibitem{pythonStyleGuide} 256 | G.\ van Rossum, B.\ Warsaw and N.\ Coghlan, 257 | ``Style Guide for Python Code,'' 258 | \url{http://legacy.python.org/dev/peps/pep-0008}. 259 | 260 | \end{thebibliography} 261 | 262 | \end{document} 263 | -------------------------------------------------------------------------------- /ellipse.m: -------------------------------------------------------------------------------- 1 | classdef ellipse < closedcurve 2 | % ELLIPSE class represents a parameterized ellipse. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | properties 12 | a = 1 13 | b = 1 14 | r = 0 15 | end 16 | 17 | properties(Access=protected) 18 | eVar 19 | end 20 | 21 | methods 22 | function E = ellipse(varargin) 23 | if nargin == 1 % epsilon case 24 | e = varargin{1}; 25 | if e < 0 || 1 <= e 26 | error('CMT:InvalidArgument', ... 27 | 'Single argument must satisfy 0 <= epsilon < 1.') 28 | end 29 | 30 | E.a = 1 + e; 31 | E.b = 1 - e; 32 | E.eVar = e; 33 | elseif nargin >= 2 34 | if nargin > 3 35 | error('CMT:InvalidArgument', 'Too many arguments.') 36 | end 37 | [E.a, E.b] = varargin{1:2}; 38 | if abs(E.a + E.b - 2) < 10*eps(2) 39 | E.eVar = E.a - 1; 40 | end 41 | if nargin > 2 42 | E.r = varargin{3}; 43 | end 44 | end 45 | end 46 | 47 | function disp(E) 48 | fprintf('parameterized ellipse:\n\n') 49 | fprintf('\tmajor axis %f\n', E.a) 50 | fprintf('\tminor axis %f\n', E.b) 51 | fprintf('\teccentricity %f\n', E.a/E.b) 52 | if ~isempty(E.eVar) 53 | fprintf('\tepsilon %f\n', E.eVar) 54 | end 55 | fprintf('\n') 56 | end 57 | 58 | function z = point(E, t) 59 | t = modparam(E, t)*2*pi; 60 | z = E.a*cos(t) + 1i*E.b*sin(t); 61 | if E.r 62 | z = z*exp(1i*E.r); 63 | end 64 | end 65 | 66 | function zt = tangent(E, t) 67 | t = modparam(E, t)*2*pi; 68 | zt = (-E.a*sin(t) + 1i*E.b*cos(t))*2*pi; 69 | if E.r 70 | zt = zt.*exp(1i*E.r); 71 | end 72 | end 73 | 74 | function th = theta_exact(E, t) 75 | % Boundary correspondence of conformal map to circle. 76 | % Exact to machine precision. 77 | % See Henrici, vol. 3, p. 391, equation for theta at bottom of page. 78 | if isempty(E.eVar) 79 | error('Must define ellipse with epsilon parameter.') 80 | end 81 | if E.eVar > 0.95 82 | warning('This is not accurate for epsilon > 0.95.') 83 | end 84 | 85 | t = modparam(E, t)*2*pi; 86 | 87 | % ellipse term magnitude function 88 | emf = @(m, e) e.^m./(1 + e.^(2*m))./m; 89 | % term function 90 | termfun = @(t, m) (-1).^m.*emf(m, E.eVar).*sin(2*m.*t); 91 | 92 | % Keep adding terms, 20 at a time, until too tiny to make a change. Good 93 | % up to about e = 0.95. 94 | th = t(:); 95 | for k = 1:60 96 | m = (k-1)*20 + (1:20); 97 | th = th + 2*sum(bsxfun(termfun, t, m), 2); 98 | if all(emf(m(end), E.eVar) < eps(th)) 99 | break 100 | end 101 | end 102 | th = reshape(th, size(t)); 103 | end 104 | end 105 | 106 | end 107 | -------------------------------------------------------------------------------- /gridcurves.m: -------------------------------------------------------------------------------- 1 | classdef gridcurves 2 | % GRIDCURVES class holds grid curves for regions. 3 | % 4 | % grid = gridcurves(curves) 5 | % The given cell array curves is stored in the object. This is mainly to 6 | % facilitate the use of plot(grid) to give a standard look to grid plots. 7 | % 8 | % Stores grid curves as entries in cell array. Consider 9 | % gd = gridcurves; 10 | % We overload gd(n) and gd(n,m) to retrieve those cell array entries. Why 11 | % not just use the '{}' syntax? Wouldn't it be clearer we're using cell arrays? 12 | 13 | % This file is a part of the CMToolbox. 14 | % It is licensed under the BSD 3-clause license. 15 | % (See LICENSE.) 16 | 17 | % Copyright Toby Driscoll, 2014. 18 | % (Re)written by Everett Kropf, 2014, 19 | % adapted from an idea by Toby Driscoll, 20??. 20 | 21 | properties 22 | curveList 23 | end 24 | 25 | methods 26 | function gc = gridcurves(curves) 27 | if ~nargin 28 | return 29 | end 30 | 31 | if nargin > 1 || ~isa(curves, 'cell') 32 | error('CMT:InvalidArgument', ... 33 | 'Expected a cell array of individual grid curves.') 34 | end 35 | 36 | gc.curveList = curves; 37 | end 38 | 39 | function gc = conj(gc) 40 | % Complex conjugation. 41 | for k = 1:numel(gc.curveList) 42 | gc.curveList{k} = conj(gc.curveList{k}); 43 | end 44 | end 45 | 46 | function disp(gd) 47 | fprintf('gridcurves object:\n\n') 48 | fprintf(' with %d gridlines.\n\n', numel(gd.curveList)) 49 | end 50 | 51 | function gc = minus(gc, b) 52 | if ~isa(gc, 'gridcurves') 53 | gc = plus(-b, gc); 54 | return 55 | end 56 | gc.scalaronly(b) 57 | for k = 1:numel(gc.curveList) 58 | gc.curveList{k} = gc.curveList{k} - b; 59 | end 60 | end 61 | 62 | function gc = mtimes(gc, b) 63 | if ~isa(gc, 'gridcurves') 64 | [gc, b] = deal(b, gc); 65 | end 66 | gc.scalaronly(b) 67 | for k = 1:numel(gc.curveList) 68 | gc.curveList{k} = b*gc.curveList{k}; 69 | end 70 | end 71 | 72 | function gc = mrdivide(gc, b) 73 | gc = rdivide(gc, b); 74 | end 75 | 76 | function n = numel(gc, varargin) 77 | % Overloaded for subsref. 78 | 79 | n = numel(gc.curveList, varargin{:}); 80 | end 81 | 82 | function gc = rdivide(gc, b) 83 | if isa(gc, 'gridcurves') 84 | gc.scalaronly(b) 85 | gc = mtimes(gc, 1/b); 86 | else 87 | [gc, b] = deal(b, gc); 88 | gc.scalaronly(b) 89 | for k = 1:numel(gc.curveList) 90 | gc.curveList{k} = b./gc.curveList{k}; 91 | end 92 | end 93 | end 94 | 95 | function out = plot(gc, varargin) 96 | washold = ishold; 97 | ah = newplot; 98 | 99 | gctag = sprintf('gridcurve_%s', num2hex(rand)); 100 | hold on 101 | 102 | [gargs, pargs] = cmtplot.gridArgs(varargin{:}); 103 | for k = 1:numel(gc.curveList) 104 | zg = gc.curveList{k}; 105 | line(real(zg), imag(zg), pargs{:}, gargs{:}, 'tag', gctag) 106 | end 107 | 108 | if ~washold 109 | hold off 110 | end 111 | 112 | if nargout 113 | out = findobj(ah, 'tag', gctag); 114 | end 115 | end 116 | 117 | function gc = plus(gc, b) 118 | if ~isa(gc, 'gridcurves') 119 | [gc, b] = deal(b, gc); 120 | end 121 | gc.scalaronly(b) 122 | for k = 1:numel(gc.curveList) 123 | gc.curveList{k} = gc.curveList{k} + b; 124 | end 125 | end 126 | 127 | function varargout = subsref(gc, S) 128 | % Provide C(j) or C(j,k) access to curve cell array. 129 | % Why? See gridcurves help. 130 | 131 | switch S(1).type 132 | case {'()', '{}'} 133 | if S(1).type(1) == '(' 134 | S(1).type = '{}'; 135 | end 136 | [varargout{1:nargout}] = subsref(gc.curveList, S); 137 | 138 | otherwise 139 | [varargout{1:nargout}] = builtin('subsref', gc, S); 140 | end 141 | end 142 | 143 | function gc = uminus(gc) 144 | for k = 1:numel(gc.curveList) 145 | gc.curveList{k} = -gc.curveList{k}; 146 | end 147 | end 148 | end 149 | 150 | methods(Access=private) 151 | function scalaronly(~, b) 152 | if ~isa(b, 'double') || numel(b) ~= 1 153 | error('CMT:NotDefined', 'Operation only defined for scalar values.') 154 | end 155 | end 156 | end 157 | 158 | end 159 | -------------------------------------------------------------------------------- /gridset.m: -------------------------------------------------------------------------------- 1 | classdef gridset < optset 2 | %GRIDSET holds grid preferences. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | 10 | properties 11 | gridType 12 | numRadialLines 13 | numCircularLines 14 | numLevels 15 | end 16 | 17 | properties(Access=protected) 18 | proplist = { ... 19 | 'gridType', 'polar', [], '[ string {polar} | carleson ]' 20 | 'numRadialLines', 20, [], '[ positive integer {20} ]' 21 | 'numCircularLines', 5, [], '[ positive integer {5} ]' 22 | 'numLevels', 5, [], '[ positive integer {5} ]' 23 | }; 24 | end 25 | 26 | methods 27 | function opts = gridset(varargin) 28 | opts = opts@optset(varargin{:}); 29 | end 30 | 31 | function opts = set.gridType(opts, value) 32 | opts.gridType = lower(value); 33 | end 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /homog.m: -------------------------------------------------------------------------------- 1 | classdef homog 2 | % HOMOG is the homogenous coordinate class. 3 | % 4 | % Replace complex number z by pair (z1, z2), such that z = z1/z2. Interacts 5 | % quietly with builtin double data type. 6 | % 7 | % zeta = homog(z1, z2) -- If z2 is not supplied, we assume z2=1. 8 | 9 | % This file is a part of the CMToolbox. 10 | % It is licensed under the BSD 3-clause license. 11 | % (See LICENSE.) 12 | 13 | % Copyright Toby Driscoll, 2014. 14 | % Written by Everett Kropf, 2014, 15 | % adapted to new classdef from Toby Driscoll's code, originally 20??. 16 | 17 | properties 18 | numerator 19 | denominator 20 | end 21 | 22 | methods 23 | function zeta = homog(z1, z2) 24 | % Constructor 25 | if nargin > 0 26 | if nargin < 2 27 | if isa(z1, 'homog') 28 | zeta = z1; 29 | return 30 | end 31 | z2 = []; 32 | end 33 | 34 | if ~isequal(size(z1), size(z2)) 35 | if isempty(z2) 36 | % Assume 1 for denominator 37 | z2 = ones(size(z1)); 38 | elseif numel(z2) == 1 39 | % Scalar expansion. 40 | z2 = repmat(z2, size(z1)); 41 | else 42 | error('Input arguments must be scalar or of the same size.') 43 | end 44 | end 45 | 46 | % Transform infinities to finite representation. There is no unique 47 | % choice, so we arbtrarily pick [\pm 1,0] based on the sign. 48 | idx = isinf(z1); 49 | z1(idx) = sign(real(z1(idx))) + 1i*sign(imag(z1(idx))); 50 | z2(idx) = 0; 51 | 52 | zeta.numerator = z1; 53 | zeta.denominator = z2; 54 | end 55 | end % ctor 56 | 57 | function r = abs(zeta) 58 | % Return absolute value. 59 | r = abs(double(zeta)); 60 | end 61 | 62 | function theta = angle(zeta) 63 | % Return phase angle, standardised to [-pi, pi). 64 | theta = mod(angle(zeta.numerator) - ... 65 | angle(zeta.denominator) + pi, 2*pi) - pi; 66 | end 67 | 68 | function zeta = cat(dim, varargin) 69 | % Override double cat(). 70 | numers = cell(nargin - 1, 1); 71 | denoms = numers; 72 | for n = 1:nargin - 1 73 | h = homog(varargin{n}); 74 | numers{n} = h.numerator; 75 | denoms{n} = h.denominator; 76 | end 77 | 78 | try 79 | zeta = homog(cat(dim, numers{:}), cat(dim, denoms{:})); 80 | catch 81 | error('Argument dimensions are not consistent.') 82 | end 83 | end 84 | 85 | function zetbar = conj(zeta) 86 | % Complex conjugate. 87 | zetbar = homog(conj(zeta.numerator), conj(zeta.denominator)); 88 | end 89 | 90 | function eta = ctranspose(zeta) 91 | % Complex transpose. 92 | eta = homog(ctranspose(zeta.numerator), ctranspose(zeta.denominator)); 93 | end 94 | 95 | function z2 = denom(zeta) 96 | % Return denominator. 97 | z2 = zeta.denominator; 98 | end 99 | 100 | function display(zeta) 101 | % Format for viewing pleasure. 102 | n = size(zeta.numerator); 103 | fprintf('\n\t%s array of homogenous coordinates:\n\n', ... 104 | [sprintf('%i-by-', n(1:end-1)), sprintf('%i', n(end))]) 105 | fprintf('numerator = \n\n') 106 | disp(zeta.numerator) 107 | fprintf('\ndenominator = \n\n') 108 | disp(zeta.denominator) 109 | end 110 | 111 | function z = double(zeta) 112 | % Convert to double. 113 | 114 | % Driscoll's original turned of divide by zero warning. Do we still need 115 | % this? Newer versions of MATLAB don't give this warning. 116 | 117 | z = zeta.numerator./zeta.denominator; 118 | % Ensure imag(z(isinf(z))) = 0 reliably. 119 | z(isinf(z)) = Inf; 120 | end 121 | 122 | function e = end(zeta, k, n) 123 | % Return array end indexes. 124 | if n == 1 125 | e = length(zeta.numerator); 126 | else 127 | e = size(zeta.numer, k); 128 | end 129 | end 130 | 131 | function zeta = horzcat(varargin) 132 | % Provide horizontal contatenation. 133 | zeta = cat(2, varargin{:}); 134 | end 135 | 136 | function z = inv(zeta) 137 | % Return 1/zeta. 138 | z = homog(zeta.denominator, zeta.numerator); 139 | end 140 | 141 | function tf = isinf(zeta) 142 | tf = zeta.denominator == 0 & zeta.numerator ~= 0; 143 | end 144 | 145 | function n = length(zeta) 146 | % Length of zeta. 147 | n = length(zeta.numerator); 148 | end 149 | 150 | function c = minus(a, b) 151 | % Provide subtraction. 152 | c = plus(a, -b); 153 | end 154 | 155 | function c = mldivide(a, b) 156 | % Provide matrix left divide. 157 | c = mtimes(inv(a), b); 158 | end 159 | 160 | function c = mrdivide(a, b) 161 | % Provide matrix right divide. 162 | c = mtimes(a, inv(b)); 163 | end 164 | 165 | function c = mtimes(a, b) 166 | % Provide multiplication. 167 | if isfloat(a) 168 | a = homog(a); 169 | end 170 | if isfloat(b) 171 | b = homog(b); 172 | end 173 | c = homog(a.numerator*b.numerator, a.denominator*b.denominator); 174 | end 175 | 176 | function n = numel(zeta, varargin) 177 | n = numel(zeta.numerator, varargin{:}); 178 | end 179 | 180 | function z1 = numer(zeta) 181 | % Return numerator. 182 | z1 = zeta.numerator; 183 | end 184 | 185 | function c = plus(a, b) 186 | % Provide addition. 187 | if isfloat(a) 188 | a = homog(a); 189 | end 190 | if isfloat(b) 191 | b = homog(b); 192 | end 193 | c = homog(a.numerator*b.denominator + a.denominator*b.numerator, ... 194 | a.denominator*b.denominator); 195 | end 196 | 197 | function c = rdivide(a, b) 198 | if isfloat(a) 199 | a = homog(a); 200 | end 201 | if isfloat(b) 202 | b = homog(b); 203 | end 204 | c = times(a, inv(b)); 205 | end 206 | 207 | function zeta = subsref(zeta, s) 208 | % Provide double-like indexing. 209 | switch s.type 210 | case '()' 211 | zeta = homog(subsref(zeta.numerator, s), subsref(zeta.denominator, s)); 212 | otherwise 213 | error('This type of indexing is not supported by homog objects.') 214 | end 215 | end 216 | 217 | function zeta = subsasgn(zeta, s, val) 218 | % Provide double-like assignment. 219 | switch s.type 220 | case '()' 221 | if length(s.subs) == 1 222 | zeta = homog(zeta); 223 | val = homog(val); 224 | index = s.subs{1}; 225 | zeta.numerator(index) = val.numerator; 226 | zeta.denominator(index) = val.denominator; 227 | else 228 | error('HOMOG objects support linear indexing only.') 229 | end 230 | otherwise 231 | error('Unspported assignment syntax.') 232 | end 233 | end 234 | 235 | function c = times(a, b) 236 | if isfloat(a) 237 | a = homog(a); 238 | end 239 | if isfloat(b) 240 | b = homog(b); 241 | end 242 | c = homog(a.numerator.*b.numerator, a.denom.*b.denominator); 243 | end 244 | 245 | function eta = transpose(zeta) 246 | % Provide basic transpose. 247 | eta = homog(transpose(zeta.numerator), transpose(zeta.denominator)); 248 | end 249 | 250 | function zeta = vertcat(varargin) 251 | % Provide vertical contatenation. 252 | zeta = cat(1, varargin{:}); 253 | end 254 | 255 | function b = uminus(a) 256 | % Unitary minus. 257 | b = homog(-a.numerator, a.denominator); 258 | end 259 | end 260 | 261 | end -------------------------------------------------------------------------------- /infvertex.m: -------------------------------------------------------------------------------- 1 | function v = infvertex(zin, zout) 2 | %INFVERTEX Create a representation of a vertex at infinity. 3 | % INFVERTEX(ZIN,ZOUT) creates an object that represents a vertex at 4 | % infinity, with an incoming straight side parallel to ZIN and an 5 | % outgoing straight side parallel to ZOUT. 6 | % 7 | % For usage examples see POLYGON. 8 | % 9 | % See also POLYGON/POLYGON, HOMOG. 10 | 11 | % This file is a part of the CMToolbox. 12 | % It is licensed under the BSD 3-clause license. 13 | % (See LICENSE.) 14 | 15 | % Copyright Toby Driscoll, 2014. 16 | 17 | v = homog([zin, zout], 0); 18 | -------------------------------------------------------------------------------- /isinpoly.m: -------------------------------------------------------------------------------- 1 | function in = isinpoly(z, vertex) 2 | % ISINPOLY checks if point z is in polygon defined by vertex array. 3 | % 4 | % Complex variable wrapper for builtin INPOLYGON. 5 | % 6 | % See also INPOLYGON. 7 | 8 | % This file is a part of the CMToolbox. 9 | % It is licensed under the BSD 3-clause license. 10 | % (See LICENSE.) 11 | 12 | % Copyright Toby Driscoll, 2014. 13 | 14 | in = inpolygon(real(z),imag(z),real(vertex),imag(vertex)); 15 | -------------------------------------------------------------------------------- /mobius.m: -------------------------------------------------------------------------------- 1 | classdef mobius < conformalmap 2 | % MOBIUS transformation class. 3 | % MOBIUS(Z,W) creates the Mobius transformation that maps the 4 | % 3-vector Z to W. One infinity is allowed in each of Z and W. 5 | % 6 | % MOBIUS(a,b,c,d) creates the transformation 7 | % 8 | % a*z + b 9 | % --------- 10 | % c*z + d 11 | % 12 | % MOBIUS([a b; c d]) is also allowed. In either of these cases, a,b,c,d 13 | % should be finite complex numbers. 14 | 15 | % This file is a part of the CMToolbox. 16 | % It is licensed under the BSD 3-clause license. 17 | % (See LICENSE.) 18 | 19 | % Copyright Toby Driscoll, 2014. 20 | % (Re)written by Everett Kropf, 2014, 21 | % adapted from code by Toby Driscoll, originally 20??. 22 | 23 | properties 24 | theMatrix 25 | end 26 | 27 | methods 28 | function M = mobius(varargin) 29 | domain = []; 30 | range = []; 31 | matrix = []; 32 | 33 | switch nargin 34 | case 1 35 | A = varargin{1}; 36 | if isa(A, 'double') && isequal(size(A), [2, 2]) 37 | matrix = A; 38 | else 39 | error('CMT:InvalidArgument', ... 40 | 'Single argument should be a 2-by-2 matrix.') 41 | end 42 | 43 | case 2 44 | [z, w] = deal(varargin{:}); 45 | if isa(z, 'circle') && isa(w, 'circle') 46 | z = point(z, pi*[0.5, 1, 1.5]); 47 | w = point(w, pi*[0.5, 1, 1.5]); 48 | end 49 | if (isa(z, 'double') && length(z) == 3) && (isa(w, 'double') && ... 50 | length(w) == 3) 51 | A1 = mobius.standardmap(z); 52 | circ = circle(z); 53 | if ~isinf(circ) 54 | domain = disk(circ); 55 | end 56 | A2 = mobius.standardmap(w); 57 | circ = circle(w); 58 | if ~isinf(circ) 59 | range = disk(circle(w)); 60 | end 61 | matrix = A2\A1; 62 | else 63 | error('CMT:InvalidArgument', ... 64 | 'Invalid arguments; see help for mobius.') 65 | end 66 | 67 | case 4 68 | matrix = reshape(cat(1, varargin{:}), [2, 2]).'; 69 | end 70 | 71 | if ~isempty(matrix) && rcond(matrix) < eps 72 | warning('Mobius map appears to be singular.') 73 | end 74 | 75 | if ~nargin 76 | supargs = {}; 77 | else 78 | supargs = {domain, range}; 79 | end 80 | M = M@conformalmap(supargs{:}); 81 | M.theMatrix = matrix; 82 | end % ctor 83 | 84 | function out = char(map) 85 | % CHAR Pretty-print a Mobius map. 86 | 87 | % Copyright (c) 1998-2006 by Toby Driscoll. 88 | 89 | % Numerator 90 | num = ''; 91 | a = map.theMatrix(1,1); 92 | if a~=0 93 | if a~=1 94 | if isreal(a) 95 | num = [num num2str(a,4) '*']; 96 | elseif isreal(1i*a) 97 | num = [num num2str(imag(a),4) 'i*']; 98 | else 99 | num = [num '(' num2str(a,4) ')*']; 100 | end 101 | end 102 | num = [num 'z']; 103 | end 104 | a = map.theMatrix(1,2); 105 | if a~=0 106 | if ~isempty(num) 107 | s = sign(real(a)); 108 | if s==0, s = sign(imag(a)); end 109 | if s > 0 110 | num = [num ' + ']; 111 | else 112 | num = [num ' - ']; 113 | a = -a; 114 | end 115 | end 116 | if isreal(a) 117 | num = [num num2str(a,4)]; 118 | elseif isreal(1i*a) 119 | num = [num num2str(imag(a),4) 'i']; 120 | else 121 | num = [num '(' num2str(a,4) ')']; 122 | end 123 | end 124 | 125 | % Denominator 126 | den = ''; 127 | a = map.theMatrix(2,1); 128 | if a~=0 129 | if a~=1 130 | if isreal(a) 131 | den = [den num2str(a,4) '*']; 132 | elseif isreal(1i*a) 133 | den = [den num2str(imag(a),4) 'i*']; 134 | else 135 | den = [den '(' num2str(a,4) ')*']; 136 | end 137 | end 138 | den = [den 'z']; 139 | end 140 | a = map.theMatrix(2,2); 141 | if a~=0 142 | if ~isempty(den) 143 | s = sign(real(a)); 144 | if s==0, s = sign(imag(a)); end 145 | if s > 0 146 | den = [den ' + ']; 147 | else 148 | den = [den ' - ']; 149 | a = -a; 150 | end 151 | end 152 | if isreal(a) 153 | den = [den num2str(a,4)]; 154 | elseif isreal(1i*a) 155 | den = [den num2str(imag(a),4) 'i']; 156 | else 157 | den = [den '(' num2str(a,4) ')']; 158 | end 159 | end 160 | 161 | L = [length(num),length(den)]; 162 | D = (max(L)-L)/2; 163 | num = [blanks(floor(D(1))) num blanks(ceil(D(1)))]; 164 | den = [blanks(floor(D(2))) den blanks(ceil(D(2)))]; 165 | fline = repmat('-',1,max(L)); 166 | 167 | out = sprintf('\n %s\n %s\n %s\n',num,fline,den); 168 | end % char 169 | 170 | function disp(f) 171 | if isempty(f.theMatrix) 172 | fprintf('\n\tempty transformation matrix\n\n') 173 | else 174 | disp(char(f)) 175 | end 176 | end 177 | 178 | function w = feval(M, z) 179 | warning('mobius.feval() is depricated, use mobius.apply() instead.') 180 | w = applyMap(M, z); 181 | end 182 | 183 | function Minv = inv(M) 184 | % Inverse transformation. 185 | 186 | % Original code turned off builtin singular matrix warning and supplied 187 | % a repeat of warning supplied in constructor. Skipping here. 188 | Minv = mobius(inv(M.theMatrix)); 189 | end 190 | 191 | function A = matrix(M) 192 | A = M.theMatrix; 193 | end 194 | 195 | function M = mrdivide(M1, M2) 196 | % Divide Mobius map by a scalar, or reciprocate it. 197 | % 1/M, for Mobius map M, swaps the numerator and denominator of M. 198 | % M/c, for scalar c, multiplies the denominator of M by c. 199 | 200 | % Copyright (c) 1998 by Toby Driscoll. 201 | % $Id: mrdivide.m,v 1.1 1998/07/01 20:14:22 tad Exp $ 202 | 203 | if isequal(M1, 1) 204 | % Exchange numerator and denominator 205 | A = M2.theMatrix; 206 | M = mobius(A([2, 1],:)); 207 | elseif isa(M2, 'double') && length(M2) == 1 208 | A = M1.theMatrix; 209 | M = mobius([1, 0; 0, M2]*A); 210 | else 211 | error('Division not defined for these operands.') 212 | end 213 | end 214 | 215 | function M = mtimes(M1, M2) 216 | % Multiply Moebius transformation by a scalar, compose it with another 217 | % map, or apply it if left multiplication. 218 | 219 | if isa(M1, 'mobius') 220 | switch class(M2) 221 | case 'mobius' 222 | M = mobius(M1.theMatrix*M2.theMatrix); 223 | M.theDomain = domain(M2); 224 | M.theRange = range(M1); 225 | return 226 | 227 | case 'conformalmap' 228 | M = mtimes@conformalmap(M2, M1); 229 | return 230 | 231 | end 232 | 233 | if isa(M2, 'closedcurve') || isa(M2, 'region') 234 | M = apply(M1, M2); 235 | return 236 | end 237 | else 238 | % swap 239 | [M1, M2] = deal(M2, M1); 240 | end 241 | 242 | % Try scalar multiplication. 243 | if isnumeric(M2) && length(M2) == 1 244 | A = M1.theMatrix; 245 | A(1,:) = A(1,:)*M2; 246 | M = mobius(A); 247 | else 248 | error('CMT:NotDefined', ['Combining %s with a Mobius ' ... 249 | 'transformation in this way is not defined.'], class(M2)) 250 | end 251 | end 252 | 253 | function z = pole(M) 254 | % Return the pole of the Moebius map. 255 | A = M.theMatrix; 256 | z = double(homog(-A(2,2)/A(2,1))); 257 | end 258 | 259 | function M = pretty(M, c) 260 | % Normalize a Moebius transformation for 'nicer' numbers. 261 | % PRETTY(M), where M is a moebius map, divides the coefficients of M 262 | % by the constant term in the demoninator, or (if that is zero) the 263 | % coefficient of the linear term in the denominator. Then any real or 264 | % imaginary parts that are close to machine precision are rounded to 265 | % exactly zero. 266 | % 267 | % PRETTY(M,C) instead normalizes all coefficients by C, then cleans up 268 | % small numbers. 269 | 270 | 271 | % Copyright (c) 2004, 2006 by Toby Driscoll. 272 | % $Id$ 273 | 274 | A = M.theMatrix; 275 | 276 | % Normalization 277 | if nargin==1 278 | d = A(2,2); 279 | if d==0 280 | d = A(2,1); 281 | end 282 | A = A/d; 283 | else 284 | A = c*A; 285 | end 286 | 287 | % Rounding 288 | index = abs(imag(A)) < 100*eps; 289 | A(index) = real(A(index)); 290 | index = abs(real(A)) < 100*eps; 291 | A(index) = 1i*imag(A(index)); 292 | 293 | M = mobius(A); 294 | end % pretty 295 | 296 | function M = uminus(M) 297 | M = -1*M; 298 | end 299 | 300 | function z = zero(M) 301 | % Return the zero of the Mobius map. 302 | A = M.theMatrix; 303 | z = double(homog(-A(1,2)/A(1,1))); 304 | end 305 | end 306 | 307 | methods(Access=protected) 308 | function w = applyMap(M, z) 309 | % Evaluate Mobius transformation. 310 | 311 | % Copyright (c) 2006 by Toby Driscoll. 312 | 313 | switch(class(z)) 314 | case {'circle', 'zline'} 315 | zp = pole(M); 316 | if dist(z, zp) < 10*eps(zp) 317 | % Result appears to be a line. 318 | zp = applyMap(M, point(z, [0.5, 1.5]*pi)); 319 | w = zline(zp); 320 | else 321 | % Find new circle using three points. 322 | zp = applyMap(M, point(z, [0.5, 1, 1.5]*pi)); 323 | w = circle(zp); 324 | end 325 | 326 | case 'double' 327 | w = NaN(size(z)); 328 | 329 | % Convert inputs to homogeneous coordinates, and reshape 330 | z = homog(z); 331 | Z = [numer(z(:)).'; denom(z(:)).']; 332 | 333 | % Apply map 334 | W = M.theMatrix*Z; 335 | 336 | % Convert to complex without DBZ warnings 337 | w(:) = double(homog(W(1,:), W(2,:))); 338 | 339 | otherwise 340 | error('Mobius maps can be applied to floats or circles/zlines only') 341 | end 342 | end 343 | end 344 | 345 | methods(Static) 346 | function A = standardmap(z) 347 | % Return mobius matrix for map from z(1:3) to [0, 1, Inf]. 348 | if isinf(z(1)) 349 | A = [0, z(2) - z(3); 1, -z(3)]; 350 | elseif isinf(z(2)) 351 | A = [1, -z(1); 1, -z(3)]; 352 | elseif isinf(z(3)) 353 | A = [1, -z(1); 0, z(2) - z(1)]; 354 | else 355 | rms = z(2) - z(3); 356 | rmq = z(2) - z(1); 357 | A = [rms, -z(1)*rms; rmq, -z(3)*rmq]; 358 | end 359 | end % standardmap 360 | end 361 | 362 | end -------------------------------------------------------------------------------- /optset.m: -------------------------------------------------------------------------------- 1 | classdef optset 2 | % OPTSET base abstract class for options structures. 3 | % 4 | % Provides a base set of functions for providing options structures. 5 | % Subclasses must provide a protected property called 'proplist', along 6 | % with public properties matching the names in the property list. 7 | % 8 | % opt = optset(varargin) 9 | % Constructor does name/value pair matching/setting based on proplist 10 | % defined in the subclass, including defaults. 11 | % 12 | % proplist (abstract property) 13 | % Property list is a n-by-4 cell array, where each nth property has the 14 | % entry 15 | % { 'name', default_value, function_handle, '[ {default} | value ]' } 16 | % The function handle is a validator function for the property, which 17 | % should return true if the value is valid for the property, and false 18 | % otherwise. This can be [], which means input will not be validated. 19 | % 20 | % Methods: 21 | % opt = set(opt, 'pref1', value1, 'pref2', value2, ...) 22 | % Sets option properties using name/value pairs. Since optset subclasses 23 | % are value classes, the action of the set must be captured as function 24 | % output. 25 | % 26 | % value = get(opt, 'pref') 27 | % Gets the value of 'pref' from optset object. 28 | % 29 | % args = varargs(opt) 30 | % Returns a cell array of name/value pairs of preferences from the object. 31 | % 32 | % An example is given in SZSET. 33 | 34 | % This file is a part of the CMToolkit. 35 | % It is licensed under the BSD 3-clause license. 36 | % (See LICENSE.) 37 | 38 | % Copyright Toby Driscoll, 2014. 39 | % Written by Everett Kropf, 2014. 40 | 41 | properties(Abstract, Access=protected) 42 | proplist 43 | end 44 | 45 | methods 46 | function opt = optset(varargin) 47 | % Assign input name-value pairs. 48 | 49 | opt = set(fillDefaults(opt), varargin{:}); 50 | end 51 | 52 | function disp(opt) 53 | % Display values for option class. 54 | 55 | nump = size(opt.proplist, 1); 56 | maxstrlen = 0; 57 | for k = 1:nump 58 | maxstrlen = max(maxstrlen, length(opt.proplist{k,1})); 59 | end 60 | 61 | fprintf('Values for %s object, with format\n', class(opt)) 62 | sep(1:maxstrlen) = '-'; 63 | fprintf(' % *s : [ description {default} ] : current value\n%s\n', ... 64 | maxstrlen, 'name', [' ' sep]); 65 | for k = 1:nump 66 | curval = evalc(sprintf('disp(opt.%s)', opt.proplist{k,1})); 67 | fprintf(' % *s : %s : %s\n', ... 68 | maxstrlen, opt.proplist{k,[1, 4]}, strtrim(curval)); 69 | end 70 | fprintf('\n') 71 | end 72 | 73 | function value = get(opt, name) 74 | value = opt.(name); 75 | end 76 | 77 | function [args, exargs] = separateArgs(opt, varargin) 78 | % Separatate 'name'/value pairs which belong to optset object and 79 | % those that don't. 80 | 81 | n = numel(varargin); 82 | if mod(n, 2) 83 | error('CMT:InvalidArgument', 'Expected name/value pairs.') 84 | end 85 | 86 | recognized = properties(opt); 87 | args = {}; 88 | idx = 1:n; 89 | for k = 1:2:n-1 90 | match = strcmpi(varargin{k}, recognized); 91 | if ~any(match) 92 | continue 93 | end 94 | args(numel(args)+(1:2)) = ... 95 | {recognized{match}, varargin{k+1}}; %#ok 96 | idx = idx(idx ~= k & idx ~= k+1); 97 | end 98 | if isempty(idx) 99 | exargs = {}; 100 | else 101 | exargs = varargin(idx); 102 | end 103 | 104 | end 105 | 106 | function [opt, exargs] = set(opt, varargin) 107 | % Set properties based on 'name'/value pairs. 108 | % 109 | % opt = set(opt, varargin) errors on unknown properties. 110 | % [opt, exargs] = set(opt, varargin) puts unknown properties in the 111 | % extargs cell array without generating an error. 112 | 113 | namelist = opt.proplist(:,1); 114 | exargs = {}; 115 | for k = 1:2:numel(varargin) 116 | j = find(strcmp(varargin{k}, namelist)); 117 | if isempty(j) 118 | if nargout > 1 119 | exargs(numel(exargs)+(1:2)) = varargin(k+(0:1)); %#ok 120 | continue 121 | else 122 | error('CMT:InvalidArgument', ... 123 | 'Preference name "%s" is not valid for class %s.', ... 124 | varargin{k}, class(opt)) 125 | end 126 | end 127 | pname = namelist{j}; 128 | isvalid = opt.proplist{j,3}; 129 | if ~isempty(isvalid) && isa(isvalid, 'function_handle') ... 130 | && ~isvalid(varargin{k+1}) 131 | error('CMT:InvalidArgument', ... 132 | ['Value for property "%s" is invalid. ' ... 133 | 'It must satisfy the function\n\t%s'], ... 134 | pname, strtrim(evalc('disp(isvalid)'))) 135 | end 136 | opt.(pname) = varargin{k+1}; 137 | end 138 | end 139 | 140 | function args = varargs(opt) 141 | % Output cell array of preferences suitable to pass as name/value 142 | % pairs in a function call. 143 | 144 | plist = properties(opt); 145 | args = cell(1, 2*numel(plist)); 146 | for k = 1:numel(plist) 147 | args(2*(k-1)+(1:2)) = {plist{k}, opt.(plist{k})}; 148 | end 149 | end 150 | end 151 | 152 | methods(Access=protected) 153 | function opt = fillDefaults(opt) 154 | % Fill structure with defined default values. 155 | 156 | names = opt.proplist(:,1); 157 | values = opt.proplist(:,2); 158 | for k = 1:numel(names) 159 | opt.(names{k}) = values{k}; 160 | end 161 | end 162 | end 163 | 164 | end 165 | -------------------------------------------------------------------------------- /plotset.m: -------------------------------------------------------------------------------- 1 | classdef plotset < optset 2 | % PLOTSET is plot settings class. 3 | % 4 | % See also optset. 5 | 6 | % This file is a part of the CMToolkit. 7 | % It is licensed under the BSD 3-clause license. 8 | % (See LICENSE.) 9 | 10 | % Copyright Toby Driscoll, 2014. 11 | % Written by Everett Kropf, 2014. 12 | 13 | properties 14 | lineWidth 15 | lineColor 16 | lineSmoothing 17 | gridColor 18 | end 19 | 20 | properties(Access=protected) 21 | proplist = { ... 22 | 'lineWidth', 0.5, @isnumeric, '[ double {0.5} ]' 23 | 'lineColor', cmtplot.black, [], '[ valid colorspec ]' 24 | 'lineSmoothing', 'on', ... 25 | @plotset.isOnOff, '[ {on} | off ]' 26 | 'gridColor', cmtplot.grey, [], '[ valid colorspec ]' 27 | } 28 | end 29 | 30 | methods 31 | function opt = plotset(varargin) 32 | opt = opt@optset(varargin{:}); 33 | end 34 | 35 | function opt = set.lineSmoothing(opt, value) 36 | opt.lineSmoothing = lower(value); 37 | end 38 | end 39 | 40 | methods(Static, Hidden) 41 | function tf = isOnOff(s) 42 | tf = any(strcmpi(s, {'on', 'off'})); 43 | end 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /polygon.m: -------------------------------------------------------------------------------- 1 | classdef polygon < closedcurve 2 | %POLYGON Contruct polygon object. 3 | % POLYGON(W) constructs a polygon object whose vertices are specified 4 | % by the complex vector W. Cusps and cracks are allowed. 5 | % 6 | % POLYGON(X,Y) specifies the vertices with two real vectors. 7 | % 8 | % POLYGON(W,ALPHA) or POLYGON(X,Y,ALPHA) manually specifies the interior 9 | % angles at the vertices, divided by pi. 10 | % 11 | % POLYGON accepts unbounded polygons (vertices at infinity). However, 12 | % you must supply ALPHA, and the vertices must be in counterclockwise 13 | % order about the interior. 14 | % 15 | % See also POLYGON/ANGLE, POLYGON/PLOT. 16 | 17 | % This file is a part of the CMToolkit. 18 | % It is licensed under the BSD 3-clause license. 19 | % (See LICENSE.) 20 | 21 | % Copyright Toby Driscoll, 2014. 22 | 23 | properties 24 | vertexList 25 | angleList 26 | end 27 | 28 | methods 29 | function P = polygon(x, y, alpha) 30 | if ~nargin 31 | return 32 | end 33 | 34 | if nargin < 3 35 | alpha = []; 36 | end 37 | if ~isreal(x) || nargin == 1 || (any(isinf(x)) && nargin==2) 38 | % Vertices passed as a complex vector 39 | w = x(:); 40 | % If first point is repeated at the end, delete the second copy 41 | % Thanks to Mark Embree for bug fix. 42 | if abs(w(end) - w(1)) < 3*eps 43 | w(end) = []; 44 | end 45 | if nargin > 1 46 | alpha = y; 47 | end 48 | else 49 | % Vertices passed as two real vectors 50 | w = x(:) + 1i*y(:); 51 | % If first point is repeated at the end, delete the second copy 52 | if abs(w(end) - w(1)) < 3*eps 53 | w(end) = []; 54 | end 55 | end 56 | 57 | P.vertexList = w(:); 58 | P.angleList = alpha(:); 59 | 60 | n = numel(w); 61 | if n > 0 62 | [alpha, isccw, index] = angle(P); 63 | if ~isccw 64 | P.vertexList = flipud(P.vertexList); 65 | alpha = flipud(alpha); 66 | end 67 | P.angleList = alpha; 68 | if abs(index) > 1 69 | warning('CMT:BadThings', 'Polygon is multiple-sheeted.') 70 | end 71 | end 72 | end % ctor 73 | 74 | function [alpha, isccw, index] = angle(p) 75 | %ANGLE Normalized interior angles of a polygon. 76 | % ALPHA = ANGLE(P) returns the interior angles, normalized by pi, of 77 | % the polygon P. 0 < ALPHA(J) <= 2 if vertex J is finite, and -2 <= 78 | % ALPHA(J) <= 0 if J is an infinite vertex. (This is consistent with 79 | % the definition as the angle swept from the exiting side through the 80 | % interior to the incoming side, with the vertices in counterclockwise 81 | % order.) It is impossible to compute angles for an unbounded polygon; 82 | % they must be supplied to the POLYGON constructor to be well-defined. 83 | % 84 | % See also POLYGON/POLYGON. 85 | 86 | w = p.vertexList; 87 | n = length(w); 88 | 89 | if ~isempty(p.angleList) 90 | % If angles have been assigned, return them 91 | alpha = p.angleList; 92 | else 93 | if isempty(w) 94 | alpha = []; 95 | isccw = []; 96 | index = []; 97 | return 98 | end 99 | 100 | if any(isinf(w)) 101 | error('CMT:InvalidArgument', ... 102 | 'Cannot compute angles for unbounded polygons.') 103 | end 104 | 105 | % Compute angles 106 | incoming = w - w([n 1:n-1]); 107 | outgoing = incoming([2:n,1]); 108 | alpha = mod(angle(-incoming.*conj(outgoing))/pi ,2); 109 | 110 | % It's ill-posed to determine locally the slits (inward-pointing) from 111 | % points (outward-pointing). Check suspicious cases at the tips to see if 112 | % they are interior to the rest of the polygon. 113 | mask = (alpha < 100*eps) | (2-alpha < 100*eps); 114 | if all(mask) 115 | % This can happen if all vertices are collinear 116 | alpha(:) = 0; 117 | isccw = 1; % irrelevant 118 | index = 1; % irrelevant 119 | return 120 | end 121 | slit = logical(isinpoly(w(mask), w(~mask))); 122 | fmask = find(mask); 123 | alpha(fmask(slit)) = 2; 124 | alpha(fmask(~slit)) = 0; 125 | end 126 | 127 | % Now test--if incorrect, assume the orientation is clockwise 128 | index = sum(alpha-1)/2; % should be integer 129 | if abs(index - round(index)) > 100*sqrt(n)*eps 130 | % Try reversing the interpretation of a crack 131 | mask = (alpha < 2*eps) | (2-alpha < 2*eps); 132 | alpha(~mask) = 2 - alpha(~mask); 133 | index = sum(alpha - 1)/2; % should be integer 134 | % If still not OK, something is wrong 135 | if abs(index - round(index)) > 100*sqrt(n)*eps 136 | error('CMT:RuntimeError', 'Invalid polygon.') 137 | end 138 | end 139 | 140 | index = round(index); 141 | isccw = (index < 0); 142 | end 143 | 144 | function box = boundbox(p) 145 | % BOUNDINGBOX Smallest box that contains the polygon. 146 | % BOUNDINGBOX(P) returns the smallest box (in AXIS format) that contains 147 | % the polygon P. If P is unbounded, all the entries will be infinite. 148 | % 149 | % See also POLYGON/DIAM. 150 | % 151 | % Copyright 2003 by Toby Driscoll. 152 | % $Id: boundingbox.m,v 1.1 2003/04/25 18:46:31 driscoll Exp $ 153 | 154 | if ~isinf(p) 155 | z = p.vertexList; 156 | box = [min(real(z)), max(real(z)), min(imag(z)), max(imag(z))]; 157 | else 158 | % We might find some finite bounds. But is there any application for this? 159 | box = inf*[-1 1 -1 1]; 160 | end 161 | end 162 | 163 | function T = cdt(p) 164 | %CDT Constrained Delaunay triangulation of polygon vertices. 165 | % T = CDT(P) returns a structure representing a constrained Delaunay 166 | % triangulation of the n polygon vertices. T has the fields: 167 | % 168 | % T.edge : 2x(2n-3) matrix of indices of edge endpoints 169 | % T.triedge : 3x(n-2) matrix of triangle edge indices 170 | % T.edgetri : 2x(2n-3) matrix of triangle membership indices for 171 | % the edges (boundary edges have a zero in 2nd row) 172 | % 173 | % See also PLOTCDT. 174 | 175 | w = p.vertexList; 176 | if any(isinf(w)) 177 | error('CMT:NotDefined', 'CDT not possible for unbounded polygons.') 178 | end 179 | 180 | [e, te, et] = crtriang(w); 181 | [e, te, et] = crcdt(w, e, te, et); 182 | 183 | T = struct('edge', e, 'triedge', te, 'edgetri', et); 184 | end 185 | 186 | function d = diam(p) 187 | %DIAM Diameter of a polygon. 188 | % 189 | % DIAM(P) returns max_{j,k} |P(j)-P(k)|. This may be infinite. 190 | 191 | w = p.vertexList; 192 | [w1, w2] = meshgrid(w); 193 | d = max(abs(w1(:) - w2(:))); 194 | end 195 | 196 | function disp(p) 197 | % Pretty-print a polygon. 198 | 199 | % Copyright 1998-2003 by Toby Driscoll. 200 | % $Id: display.m,v 2.4 2003/05/08 18:11:36 driscoll Exp $ 201 | 202 | w = p.vertexList; 203 | n = numel(w); 204 | 205 | if isempty(w) 206 | fprintf('\n empty polygon object\n\n') 207 | return 208 | end 209 | 210 | fprintf('\n polygon object:\n\n') 211 | 212 | % We make disp do the heavy lifting. This way the FORMAT command works 213 | % here too. 214 | 215 | vstr = evalc('disp(w)'); 216 | astr = evalc('disp(p.angleList)'); 217 | 218 | % Parse into one cell per line. 219 | vc = textscan(vstr, '%s', n, 'delimiter', '\n'); 220 | vc = vc{1}; 221 | ac = textscan(astr, '%s', n, 'delimiter', '\n'); 222 | ac = ac{1}; 223 | 224 | % Now into matrices. 225 | vm = char(vc); 226 | am = char(ac); 227 | 228 | 229 | % Remove leading and trailing space blocs. 230 | % (Should use strtrim here? -- EK) 231 | idx = find(~all(vm == ' ')); 232 | vm = vm(:,min(idx):max(idx)); 233 | idx = find(~all(am == ' ')); 234 | am = am(:,min(idx):max(idx)); 235 | 236 | wv = max(size(vm, 2), 6); 237 | wa = max(size(am, 2), 8); 238 | b1 = blanks(2 + floor((wv - 6)/2)); 239 | b2 = blanks(ceil((wv - 6)/2) + 4 + floor((wa - 8)/2)); 240 | fprintf([b1 'Vertex' b2 'Angle/pi\n']); 241 | 242 | uv = min(size(vm, 2), 6); 243 | ua = min(size(am, 2), 8); 244 | b1 = blanks(2 + floor((6 - uv)/2)); 245 | b2 = blanks(ceil((6 - uv)/2) + 4 + floor((8 - ua)/2)); 246 | str = [repmat(b1, n, 1), vm, repmat(b2, n, 1), am]; 247 | 248 | fprintf([' ' repmat('-', 1, wv+4+wa) '\n']); 249 | disp(str) 250 | fprintf('\n\n') 251 | end % disp 252 | 253 | function x = double(p) 254 | % DOUBLE Convert polygon to double. 255 | % If the polygon is bounded, DOUBLE returns the vertices in an Nx2 256 | % matrix. Otherwise, it returns a cell array whose first component is 257 | % the vertex matrix and whose second component is the vector of 258 | % interior normalized angles. 259 | 260 | % Copyright 1998 by Toby Driscoll. 261 | % $Id: double.m,v 2.1 1998/05/10 03:51:49 tad Exp $ 262 | 263 | if ~any(isinf(p.vertexList)) 264 | x = p.vertexList; 265 | x = [real(x), imag(x)]; 266 | else 267 | x = {[real(p.vertexList), imag(p.vertexList)], p.angleList}; 268 | end 269 | end 270 | 271 | function j = end(p, ~, ~) 272 | j = length(p); 273 | end 274 | 275 | function H = fill(p, varargin) 276 | % FILL Plot a polygon with a filled interior. 277 | % FILL(P) plots the boundary of P in blue and fills the interior of the 278 | % polygon with gray. FILL(P,PROP1,VAL1,...) passes additional arguments 279 | % to the built-in FILL command. 280 | % 281 | % See also FILL. 282 | 283 | % Copyright 2003 by Toby Driscoll. 284 | % $Id: fill.m,v 1.3 2004/05/27 13:11:21 driscoll Exp $ 285 | 286 | v = p.vertexList; 287 | vf = v(~isinf(v)); 288 | if any(isinf(v)) 289 | v = vertex(truncate(p)); 290 | end 291 | 292 | axlim = [min(real(vf)), max(real(vf)), min(imag(vf)), max(imag(vf))]; 293 | d = max([diff(axlim(1:2)), diff(axlim(3:4))]); 294 | if d < eps 295 | d = 1; 296 | end 297 | axlim(1:2) = mean(axlim(1:2)) + 0.54*[-1 1]*d; 298 | axlim(3:4) = mean(axlim(3:4)) + 0.54*[-1 1]*d; 299 | 300 | % Use defaults, but allow overrides and additional settings. 301 | settings = {[0.75 0.75 0.85], 'edgecolor', 'b', ... 302 | 'linewidth', 1.5, varargin{:}}; %#ok 303 | v = v([1:end, 1]); 304 | h = fill(real(v), imag(v), settings{:}); 305 | 306 | if ~ishold 307 | axis equal 308 | axis square 309 | axis(axlim) 310 | end 311 | 312 | if nargout 313 | H = h; 314 | end 315 | end 316 | 317 | function [hits, loc] = intersect(p, endpt, tol) 318 | %INTERSECT Find intesection of segments with polygon sides. 319 | % 320 | % S = INTERSECT(P,ENDPT) checks for intesections between sides of the 321 | % polygon P and the line segments whose endpoints are given in the 322 | % complex M by 2 matrix ENDPT. If P has N sides, on return S is an 323 | % M by N logical matrix with nonzeros at locations indicating 324 | % intersection. 325 | % 326 | % INTERSECT(P,ENDPT,TOL) requires that the intersection take place 327 | % more than TOL away (relatively) from the segments' endpoints. By 328 | % default TOL=EPS. To test truly closed segments, use 329 | % INTERSECT(P,ENDPT,0); however, this is a poorly conditioned 330 | % problem. 331 | 332 | n = numel(p); 333 | if nargin < 3 334 | tol = eps; 335 | end 336 | 337 | m = size(endpt,1); 338 | w = vertex(p); 339 | beta = angle(p)-1; 340 | 341 | % Where are the slits? 342 | isslit = abs(beta-1) < 2*eps; 343 | isslit = isslit | isslit([2:n 1]); 344 | 345 | % Find two consecutive finite vertices. 346 | dw = diff( w([1:n 1]) ); 347 | K = find(~isinf(dw), 1); 348 | % Arguments of polygon sides. 349 | argw = ones(n,1); 350 | argw([K:n 1:K-1]) = cumsum( [angle(dw(K));-pi*beta([K+1:n 1:K-1])] ); 351 | 352 | % Check each side. Solve for two parameters and check their ranges. 353 | hits = false(m, n); 354 | loc = nan(m, n); 355 | for k = 1:n 356 | tangent = exp(1i*argw(k)); 357 | if ~isinf(w(k)) 358 | wk = w(k); 359 | s1max = abs( w(rem(k,n)+1)-w(k) ); % parameter in [0,s1max] 360 | else 361 | % Start from next vertex and work back. 362 | wk = w(rem(k,n)+1); 363 | tangent = -tangent; 364 | s1max = Inf; 365 | end 366 | A(:,1) = [ real(tangent); imag(tangent) ]; 367 | 368 | % Loop over the segments to be tested. The alternative is to solve a 369 | % block 2x2 diagonal matrix, but any collinear cases would ruin the 370 | % whole batch. 371 | for j = 1:m 372 | e1e2 = endpt(j,2) - endpt(j,1); 373 | A(:,2) = -[ real(e1e2); imag(e1e2) ]; 374 | if rcond(A) < 2*eps 375 | % Segments are parallel. Check for collinearity using rotation. 376 | e2 = (endpt(j,2)-wk) / tangent; 377 | e1 = (endpt(j,1)-wk) / tangent; 378 | if abs(imag(e1)) < 2*eps 379 | % Check for overlapping. 380 | x1 = min( real([e1 e2]) ); 381 | x2 = max( real([e1 e2]) ); 382 | % Do these values straddle either of the side's endpoints? 383 | if (x2 >= tol) && (x1 <= s1max-tol) 384 | hits(j,k) = 1; 385 | loc(j,k) = wk; % pick a place 386 | end 387 | end 388 | else 389 | % Generic case. Find intersection. 390 | delta = endpt(j,1) - wk; 391 | s = A \ [real(delta);imag(delta)]; 392 | % Check parameter ranges. 393 | if s(1)>=-eps && s(1)<=s1max+eps && s(2)>=tol && s(2)<=1-tol 394 | % If an end of the segment lies on a slit side, check for 395 | % interior vs. exterior. 396 | if isslit(k) && (abs(s(2)) < 10*eps) 397 | normal = 1i*tangent; 398 | if real( conj(e1e2)*normal ) < 0, break, end 399 | elseif isslit(k) && (abs(s(2)-1) < 10*eps) 400 | normal = 1i*tangent; 401 | if real( conj(e1e2)*normal ) > 0, break, end 402 | end 403 | hits(j,k) = 1; 404 | loc(j,k) = wk + s(1)*tangent; 405 | end 406 | end 407 | end 408 | end 409 | end 410 | function t = isempty(p) 411 | % Returns true if there are no vertices. 412 | 413 | % Copyright 1998 by Toby Driscoll. 414 | % $Id: isempty.m,v 2.1 1998/05/10 03:52:39 tad Exp $ 415 | 416 | t = isempty(p.vertexList); 417 | end 418 | 419 | function tf = isinf(p) 420 | % Is the polygon unbounded? 421 | 422 | tf = any(isinf(p.vertexList)); 423 | end 424 | 425 | % FIXME: This function looks static. 426 | function idx = isinpoly(wp, p, varargin) 427 | % ISINPOLY Identify points interior/exterior to a polygon. 428 | % ISINPOLY(WP,P) returns a logical vector the size of WP in which 429 | % nonzero means the corresponding point is inside polygon P and zero 430 | % means it is outside. 431 | % 432 | % ISINPOLY(WP,P,TOL) considers points within TOL of the boundary to be 433 | % inside P. Without this argument, points on the boundary may or may not 434 | % register as inside. 435 | % 436 | % See also POLYGON/WINDING. 437 | 438 | idx = logical(winding(p, wp, varargin{:})); 439 | end 440 | 441 | function in = isinside(p, z) 442 | % Wrapper for builtin INPOLYGON. 443 | 444 | v = p.vertexList; 445 | in = inpolygon(real(z), imag(z), real(v), imag(v)); 446 | end 447 | 448 | function n = length(p) 449 | % Returns number of vertices, NOT the polygon boundary length. 450 | % FIXME: To be consistent with other boundaries, this should return 451 | % boundary length. Use numel(vertex(p)) instead of this function! 452 | 453 | n = numel(p.vertexList); 454 | end 455 | 456 | function [z, idx] = linspace(p, m) 457 | % LINSPACE Evenly spaced points around the polygon. 458 | % LINSPACE(P,N) returns a vector of N points evenly spaced on the 459 | % polygon P, starting with the first vertex. 460 | % 461 | % LINSPACE(P,H) for H<1 instead uses H as an upper bound on the arc 462 | % length between points. 463 | % 464 | % [Z,IDX] = LINSPACE(...) returns the points and an identically sized 465 | % vector of corresponding side indices. 466 | % 467 | % If the polygon is unbounded, an error results. 468 | 469 | w = p.vertexList; 470 | if any(isinf(w)) 471 | error('CMT:NotDefined', 'Invalid on unbounded polygons.') 472 | end 473 | 474 | n = numel(w); 475 | dw = diff(w([1:n, 1])); 476 | 477 | % Arc lengths of sides. 478 | s = abs(dw); 479 | s = cumsum([0; s]); 480 | L = s(end); 481 | s = s/L; % relative arc length 482 | 483 | % Evenly spaced points in arc length. 484 | if m < 1 485 | % How many points needed? 486 | m = ceil(L/m) + 1; 487 | end 488 | zs = (0:m-1)'/m; 489 | z = zs; 490 | done = false(size(z)); 491 | idx =zeros(size(z)); 492 | 493 | % Translate to polygon sides. 494 | for j = 1:n 495 | mask = ~done & zs < s(j+1); 496 | z(mask) = w(j) + dw(j)*(zs(mask) - s(j))/(s(j+1) - s(j)); 497 | idx(mask) = j; 498 | done = mask | done; 499 | end 500 | end 501 | 502 | function [p, indx] = modify(p) 503 | %MODIFY Modify a polygon graphically. 504 | % See MODPOLY for usage instructions. 505 | 506 | [w, beta, indx] = modpoly(vertex(p), angle(p) - 1); 507 | p = polygon(w, beta + 1); 508 | end 509 | 510 | function r = minus(p, q) 511 | % Translate a polygon, or subtract the vertices of two polygons. 512 | r = plus(p, -q); 513 | end 514 | 515 | function r = mrdivide(p, q) 516 | % Divide a polygon by a scalar. 517 | if ~isa(q, 'double') || numel(q) > 1 518 | error('CMT:NotDefined', ... 519 | 'Right division of a polygon defined only for a scalar double.') 520 | end 521 | 522 | r = p; 523 | r.vertexList = r.vertexList/q; 524 | end 525 | 526 | function r = mtimes(p, q) 527 | % Multiply polygon by a scalar. 528 | if isa(q, 'polygon') 529 | if isa(p, 'polygon') 530 | error('CMT:NotDefined', ... 531 | 'Operator "*" not defined for two polygon objects.') 532 | end 533 | [q, p] = deal(p, q); 534 | end 535 | 536 | r = p; 537 | r.vertexList = r.vertexList*q; 538 | end 539 | 540 | function L = perimeter(p) 541 | % PERIMETER Perimeter length of a polygon. 542 | 543 | if isinf(p) 544 | L = inf; 545 | else 546 | w = p.vertexList; 547 | L = sum(abs(diff(w([1:end, 1])))); 548 | end 549 | end 550 | 551 | function h = plotcdt(p,T,varargin) 552 | %PLOTCDT Plot constrained Delaunay triangulation. 553 | % PLOTCDT(P,T) plots the CDT of P computed by CDT. PLOTCDT(P,T,1) labels 554 | % the edges and vertices. 555 | % 556 | % H = PLOTCDT(P,T) returns a vector of handles for the edges. 557 | % 558 | % See also CDT. 559 | 560 | han = sctool.plotptri(p.vertex, T.edge, varargin{:}); 561 | 562 | if nargout > 0 563 | h = han; 564 | end 565 | end 566 | 567 | function box = plotbox(p, scale) 568 | 569 | if nargin < 2 || isempty(scale) 570 | scale = 1.2; 571 | end 572 | atinf = isinf(p.vertexList); 573 | zf = p.vertexList(~atinf); 574 | box = [min(real(zf)), max(real(zf)), min(imag(zf)), max(imag(zf))]; 575 | maxdiff = max(diff(box(1:2)), diff(box(3:4))); 576 | if maxdiff < 100*eps 577 | maxdiff = 1; 578 | end 579 | fac = scale*(0.5 + 0.125*any(atinf)); 580 | box(1:2) = mean(box(1:2)) + fac*maxdiff*[-1 1]; 581 | box(3:4) = mean(box(3:4)) + fac*maxdiff*[-1 1]; 582 | end 583 | 584 | function r = plus(p, q) 585 | % Translate a polygon, or add the vertices of two polygons. 586 | 587 | if isa(q, 'polygon') 588 | [q, p] = deal(p, q); 589 | end 590 | 591 | switch class(q) 592 | case 'polygon' 593 | if numel(q.vertexList) ~= numel(p.vertexList) 594 | error('Polygons mst have the same length to be added.') 595 | elseif isinf(p) || isinf(q) 596 | error('Only finite polygons may be added.') 597 | end 598 | r = polygon(p.vertexList + q.vertexList); 599 | 600 | case 'double' 601 | if numel(q) > 1 && numel(q) ~= numel(p.vertexList) 602 | error(['Only a scalar or identical-length vector may be added ' ... 603 | 'to a polygon.']) 604 | end 605 | r = polygon(p.vertexList + q(:)); 606 | end 607 | end 608 | 609 | function z = point(p, t) 610 | % Boundary point by parameter t in [0, 1]. 611 | n = numel(p.vertexList); 612 | zc = p.vertexList; 613 | zh = p.hvertex_; 614 | zhind = p.hindex_; 615 | z = nan(size(t)); 616 | t = modparam(p, t(:)); 617 | 618 | % Loop by side number. 619 | for k = 1:n 620 | sidek = find(t >= (k - 1)/n & t < k/n); 621 | if isempty(sidek) 622 | continue 623 | end 624 | tk = n*t(sidek) - (k - 1); 625 | k1 = mod(k, n) + 1; 626 | if isinf(zc(k)) 627 | tangent = numer(zh(zhind(k) + 1)); 628 | z(sidek) = double(zc(k1) + homog((1 - tk)*tangent, tk)); 629 | elseif isinf(zc(k1)) 630 | tangent = numer(zh(zhind(k1))); 631 | z(sidek) = double(zc(k) + homog(tk*tangent, 1 - tk)); 632 | else 633 | z(sidek) = interp1([0 1], zc([k k1]), tk, 'linear'); 634 | end 635 | end 636 | end 637 | 638 | function n = size(p, m) 639 | % Number of vertices. 640 | 641 | if nargin == 1 642 | n = [numel(p.vertexList), 1]; 643 | elseif m ==1 644 | n = numel(p.vertexList); 645 | else 646 | n = 1; 647 | end 648 | end 649 | 650 | function p = subsasgn(p, S, data) 651 | % Allows individual vertex assignment or property modification. 652 | 653 | if length(S) == 1 && strcmp(S.type, '()') && length(S.subs) == 1 654 | % Single index reference. 655 | p.vertexList(S.subs{1}) = data; 656 | else 657 | p = builtin('subsasgn', p, S, data); 658 | end 659 | end 660 | 661 | function out = subsref(p, S) 662 | % Extract vertices by index or act as property reference. 663 | 664 | % Vertex reference. 665 | if length(S) == 1 && strcmp(S.type, '()') 666 | out = subsref(p.vertexList, S); 667 | return 668 | end 669 | 670 | % Property reference. 671 | out = builtin('subsref', p, S); 672 | end 673 | 674 | function zt = tangent(p, t) %#ok 675 | error('CMT:NotImplemented', ... 676 | 'Placeholder function waiting on implementation.') 677 | end 678 | 679 | function [tri, x, y] = triangulate(p, h) 680 | %TRIANGULATE Triangulate the interior of a polygon. 681 | % 682 | % [TRI,X,Y] = TRIANGULATE(P,H) attempts to find a reasonable 683 | % triangulation of the interior of polygon P so that the typical 684 | % triangle diameter is about H. If H is not specified, an automatic 685 | % choice is made. 686 | % 687 | % If P is unbounded, the polygon is first truncated to fit in a 688 | % square. 689 | % 690 | % TRIANGULATE uses DELAUNAY from Qhull, and as such does not have 691 | % guaranteed success for nonconvex regions. However, things should 692 | % go OK unless P has slits. 693 | % 694 | % See also TRUNCATE, DELAUNAY. 695 | 696 | if isinf(p) 697 | warning('Truncating an unbounded polygon.') 698 | p = truncate(p); 699 | end 700 | 701 | w = vertex(p); 702 | n = length(w); 703 | 704 | if nargin < 2 705 | h = diam(p) / 40; 706 | end 707 | 708 | % Find points around boundary. 709 | [wb, idx] = linspace(p, h/2); % smaller spacing to help qhull 710 | 711 | % On sides of a slit, put on extra points and perturb inward a little. 712 | isslit = ( abs(angle(p)-2) < 10*eps ); 713 | slit = find( isslit | isslit([2:n 1]) ); 714 | [wbfine, idxfine] = linspace(p, h/6); 715 | for k = slit(:)' 716 | new = (idxfine==k); 717 | old = (idx==k); 718 | wb = [ wb(~old); wbfine(new) ]; idx = [ idx(~old); idxfine(new) ]; 719 | move = find(idx==k); 720 | normal = 1i*( w(rem(k,n)+1) - w(k) ); 721 | wb(move) = wb(move) + 1e-8*normal; 722 | end 723 | 724 | % Take out points that are fairly close to a singularity, because it's 725 | % hard to find the inverse mapping there. 726 | for k = find(angle(p)<1)' 727 | close = abs(wb - w(k)) < h/3; 728 | wb(close) = []; idx(close) = []; 729 | end 730 | 731 | % Add the polygon vertices. 732 | wb = [ wb; w ]; 733 | 734 | % Not used? EK, 14-08-2014. 735 | % idx = [ idx; (1:n)']; 736 | 737 | % Find a hex pattern that covers the interior. 738 | xlim = [ min(real(w)) max(real(w)) ]; 739 | ylim = [ min(imag(w)) max(imag(w)) ]; 740 | x = linspace(xlim(1),xlim(2),ceil(diff(xlim))/h+1); 741 | y = linspace(ylim(1),ylim(2),ceil(diff(ylim))/h+1); 742 | [X,Y] = meshgrid(x(2:end-1),y(2:end-1)); 743 | X(2:2:end,:) = X(2:2:end,:) + (x(2)-x(1))/2; 744 | 745 | inside = isinpoly(X+1i*Y,p); 746 | x = [ real(wb); X(inside) ]; 747 | y = [ imag(wb); Y(inside) ]; 748 | 749 | % Triangulate using qhull. 750 | tri = delaunay(x,y); 751 | 752 | % Those with a boundary vertex must be examined. 753 | nb = length(wb); 754 | check = find( any( tri<=nb, 2 ) ); 755 | 756 | % First, check triangle midpoints. 757 | idx = tri(check,:); 758 | z = x(idx) + 1i*y(idx); 759 | out = ~isinpoly( sum(z,2)/3, p ); 760 | 761 | % On the rest, look for edges that cross two slit sides. 762 | check2 = find(~out); 763 | sect1 = intersect(p,z(check2,[1 2]),1e-6); 764 | sect2 = intersect(p,z(check2,[2 3]),1e-6); 765 | sect3 = intersect(p,z(check2,[3 1]),1e-6); 766 | out(check2( sum(sect1,2) > 1 )) = 1; 767 | out(check2( sum(sect2,2) > 1 )) = 1; 768 | out(check2( sum(sect3,2) > 1 )) = 1; 769 | 770 | tri(check(out),:) = []; 771 | end 772 | 773 | function q = truncate(p) 774 | % TRUNCATE Truncate an unbounded polygon. 775 | % Q = TRUNCATE(P) returns a polygon whose finite vertices are the same 776 | % as those of P and whose infinite vertices have been replaced by 777 | % several finite ones. The new vertices are chosen by using a 778 | % circular "cookie cutter" on P. 779 | 780 | w = p.vertexList; 781 | n = numel(w); 782 | if ~any(isinf(w)) 783 | q = p; 784 | return 785 | end 786 | 787 | % Put the finite vertices in a box. 788 | wf = w(~isinf(w)); 789 | xbound = [ min(real(wf)); max(real(wf)) ]; 790 | ybound = [ min(imag(wf)); max(imag(wf)) ]; 791 | delta = max( diff(xbound), diff(ybound) ); 792 | xrect = mean(xbound) + [-1;1]*delta; 793 | yrect = mean(ybound) + [-1;1]*delta; 794 | zrect = xrect([1 1 2 2]') + 1i*yrect([2 1 1 2]'); 795 | 796 | % Find intersections of the box with the unbounded sides. 797 | [hit, loc] = intersect(p, [zrect, zrect([2:4, 1])]); 798 | 799 | % Carry over the finite vertices, inserting to substitute for the 800 | % infinite ones. 801 | atinf = find(isinf(w)); 802 | v = w(1:atinf(1)-1); 803 | for k = 1:length(atinf) 804 | M = atinf(k); 805 | M1 = mod(M-2, n) + 1; 806 | 807 | % Find where the adjacent sides hit the rectangle. 808 | rp = loc(logical(hit(:,M1)),M1); 809 | % sp = find( hit(:,M1) ); 810 | % rp = loc(sp,M1); 811 | rn = loc(logical(hit(:,M)),M); 812 | % sn = find( hit(:,M) ); 813 | % rn = loc(sn,M); 814 | 815 | % Include the rectangle corners that are "in between". 816 | dt = mod( angle(rn/rp), 2*pi ); 817 | dr = mod( angle(zrect/rp), 2*pi ); 818 | [dr,idx] = sort(dr); 819 | use = dr 821 | if k < length(atinf) 822 | v = [ v; w(M+1:atinf(k+1)-1) ]; %#ok 823 | else 824 | v = [ v; w(M+1:end) ]; %#ok 825 | end 826 | end 827 | 828 | q = polygon(v); 829 | end 830 | 831 | function q = uminus(p) 832 | % Negate the vertices of a polygon. 833 | % This may have surprising consequences if p is unbounded. 834 | 835 | q = polygon(-p.vertexList, p.angleList); 836 | end 837 | 838 | function [x, y] = vertex(p) 839 | %VERTEX Vertices of a polygon. 840 | % VERTEX(P) returns the vertices of polygon P as a complex vector. 841 | % 842 | % [X,Y] = VERTEX(P) returns the vertices as two real vectors. 843 | % 844 | % See also POLYGON. 845 | 846 | x = p.vertexList; 847 | if nargout == 2 848 | y = imag(x); 849 | x = real(x); 850 | end 851 | end 852 | 853 | function idx = winding(p, wp, varargin) 854 | % WINDING Winding number of points with respect to a polygon. 855 | % WINDING(P,WP) returns a vector the size of WP of winding numbers with 856 | % respect to P. A zero value means the point is outside P; a value 857 | % greater than 1 means it lies on multiple sheets. 858 | % 859 | % WINDING(P,WP,TOL) makes the boundary of P "fuzzy" by a distance 860 | % TOL. This may be needed to compute winding number for points on the 861 | % boundary that you want to be considered "just inside." 862 | % 863 | % See also POLYGON/ISINPOLY. 864 | 865 | if isinf(p) 866 | warning('CMT:BadThings', ... 867 | 'Using a truncated version of the polygon.') 868 | p = truncate(p); 869 | end 870 | 871 | idx = double(isinpoly(wp, p.vertexList, varargin{:})); 872 | end 873 | end 874 | 875 | methods(Hidden) 876 | function handle = plotCurve(p, varargin) 877 | % PLOT a polygon. 878 | 879 | if isempty(p.vertexList) 880 | return 881 | end 882 | 883 | % An unbounded polygon will be truncated to make it safe for plotting. 884 | zplot = vertex(truncate(p)); 885 | 886 | zplot = zplot([1:end 1]); 887 | [cargs, pargs] = cmtplot.closedcurveArgs(varargin{:}); 888 | h = plot(real(zplot), imag(zplot), pargs{:}, cargs{:}); 889 | 890 | if nargout 891 | handle = h; 892 | end 893 | end 894 | end 895 | 896 | end 897 | -------------------------------------------------------------------------------- /region.m: -------------------------------------------------------------------------------- 1 | classdef region < cmtobject 2 | % REGION class. 3 | % 4 | % A region is defined by one or more closed curves. The interior of a 5 | % region is defined to be to the left of the tangent vector of the closed 6 | % curve. The exterior is the compliment of the interior. 7 | % 8 | % TBD: What does it mean for a region object to have no boundary curves? 9 | % Currently isempty() returns true if there are no boundary curves, which 10 | % is admittedly a bit ambiguously named. Do we mean an empty set for a 11 | % region, or do we mean the entire plane? 12 | % 13 | % r = region(p) 14 | % r = region(p, 'interiorto') 15 | % Constructs an interior region bounded by the closed curve p or the 16 | % cell array of closed curves p. 17 | % r = region(q, 'exteriorto') 18 | % Constructs an exterior region bounded by the closed curve q or the cell 19 | % array of closed curves q. 20 | % r = region(p, q) 21 | % r = region(p, 'interiorto', q, 'exteriorto') 22 | % r = region(q, 'exteriorto', p, 'interiorto') 23 | % Constructs a region with p as the exterior boundary and q as the 24 | % interior boundary. The arguments may be cell arrays. 25 | % 26 | % See also closedcurve. 27 | 28 | % This file is a part of the CMToolbox. 29 | % It is licensed under the BSD 3-clause license. 30 | % (See LICENSE.) 31 | 32 | % Copyright Toby Driscoll, 2014. 33 | % Written by Everett Kropf, 2014, 34 | % adapted from code by Toby Driscoll, 20??. 35 | 36 | properties 37 | outerboundary 38 | innerboundary 39 | end 40 | 41 | properties(Dependent) 42 | m 43 | numinner 44 | numouter 45 | end 46 | 47 | methods 48 | function R = region(varargin) 49 | if ~nargin 50 | return 51 | end 52 | 53 | if isa(varargin{1}, 'region') 54 | R = varargin{1}; 55 | return 56 | end 57 | 58 | switch nargin 59 | case 1 60 | p = varargin{1}; 61 | R.outerboundary = region.checkcc(p); 62 | 63 | case 2 64 | [p, q] = varargin{:}; 65 | if ischar(q) 66 | switch q 67 | case 'interiorto' 68 | R.outerboundary = region.checkcc(p); 69 | case 'exteriorto' 70 | R.innerboundary = region.checkcc(p); 71 | otherwise 72 | error('CMT:InvalidArgument', 'String "%s" unrecognized.', q) 73 | end 74 | else 75 | R.outerboundary = region.checkcc(p); 76 | R.innerboundary = region.checkcc(q); 77 | end 78 | 79 | case 4 80 | [p, pstr, q, qstr] = varargin{:}; 81 | if strcmp(pstr, 'interiorto') && strcmp(qstr, 'exteriorto') 82 | R.outerboundary = region.checkcc(p); 83 | R.innerboundary = region.checkcc(q); 84 | elseif strcmp(qstr, 'interiorto') && strcmp(pstr, 'exteriorto') 85 | R.outerboundary = region.checkcc(q); 86 | R.innerboundary = region.checkcc(p); 87 | else 88 | error('Invalid arguemts. Seek help.') 89 | end 90 | 91 | otherwise 92 | error('Argument configuration unrecognized.') 93 | end 94 | end % ctor 95 | 96 | function b = boundary(R) 97 | % List region boundary as cell array of closedcurves. 98 | 99 | % Not clear the best way to do this, especially for multiply 100 | % connected regions. Just dump for now. 101 | if hasouter(R) 102 | b = R.outerboundary; 103 | else 104 | b = {}; 105 | end 106 | if hasinner(R) 107 | b = [b(:); R.innerboundary(:)]; 108 | end 109 | if numel(b) == 1 110 | b = b{1}; 111 | end 112 | end 113 | 114 | function box = boundbox(R) 115 | zi = zeros(4*R.numinner, 1); 116 | for k = 1:R.numinner 117 | zi(4*(k - 1) + (1:4)) = cmt.bb2z(boundbox(R.innerboundary{k})); 118 | end 119 | zo = zeros(4*R.numouter, 1); 120 | for k = 1:R.numouter 121 | zo(4*(k - 1) + (1:4)) = cmt.bb2z(boundbox(R.outerboundary{k})); 122 | end 123 | box = cmt.boundbox([zi; zo]); 124 | end 125 | 126 | function m = connectivity(R) 127 | m = R.numinner + R.numouter; 128 | end 129 | 130 | function disp(R) 131 | if isempty(R) 132 | fprintf('empty region\n\n') 133 | end 134 | 135 | fprintf('region') 136 | 137 | outer = R.outerboundary; 138 | inner = R.innerboundary; 139 | 140 | if ~isempty(outer) 141 | fprintf(' interior to:\n') 142 | for k = 1:numel(outer) 143 | disp(outer{k}) 144 | end 145 | if ~isempty(inner) 146 | fprintf('\n and') 147 | end 148 | end 149 | 150 | if ~isempty(inner) 151 | fprintf(' exterior to:\n') 152 | for k = 1:numel(inner) 153 | disp(inner{k}) 154 | end 155 | end 156 | end 157 | 158 | function gd = grid(~) 159 | % Default empty grid. 160 | gd = []; 161 | end 162 | 163 | function m = get.m(R) 164 | m = connectivity(R); 165 | end 166 | 167 | function n = get.numinner(R) 168 | n = numel(R.innerboundary); 169 | end 170 | 171 | function n = get.numouter(R) 172 | n = numel(R.outerboundary); 173 | end 174 | 175 | function fill(R, varargin) 176 | % Fill plot of region. 177 | 178 | newplot 179 | washold = ishold; 180 | hold on 181 | 182 | fillargs = cmtplot.fillargs; 183 | 184 | % Fill interiors of any outer boundaries or draw exterior region. 185 | if hasouter(R) 186 | for k = 1:R.numouter 187 | fill(R.outerboundary{k}, varargin{:}) 188 | end 189 | elseif isexterior(R) 190 | zb = zeros(4*R.numinner, 1); 191 | for k = 1:R.numinner 192 | zb(4*(k - 1) + (1:4)) = cmt.bb2z(plotbox(R.innerboundary{k}, 1)); 193 | end 194 | zb = cmt.bb2z(cmt.plotbox(zb, 2)); 195 | fill(real(zb), imag(zb), fillargs{:}, varargin{:}); 196 | end 197 | 198 | % Poke holes based on inner boundaries. 199 | if hasinner(R) 200 | bgcolor = get(gca, 'color'); 201 | for k = 1:R.numinner 202 | fill(R.innerboundary{k}, 'facecolor', bgcolor, ... 203 | 'edgecolor', cmtplot.filledgecolor, varargin{:}) 204 | end 205 | end 206 | 207 | if ~washold 208 | hold off 209 | axis(plotbox(R)) 210 | aspectequal 211 | end 212 | end % fill 213 | 214 | function tf = hasgrid(~) 215 | % No default grid. 216 | tf = false; 217 | end 218 | 219 | function tf = hasinner(R) 220 | tf = ~isempty(R.innerboundary); 221 | end 222 | 223 | function tf = hasouter(R) 224 | tf = ~isempty(R.outerboundary); 225 | end 226 | 227 | function b = inner(R) 228 | if R.numinner == 1 229 | b = R.innerboundary{1}; 230 | else 231 | b = R.innerboundary; 232 | end 233 | end 234 | 235 | function tf = isempty(R) 236 | % Empty region? 237 | tf = isempty(R.outerboundary) & isempty(R.innerboundary); 238 | end 239 | 240 | function tf = isexterior(R) 241 | % True if region has only inner boundaries. False otherwise. 242 | tf = hasinner(R) & ~hasouter(R); 243 | end 244 | 245 | function tf = isin(R, z) 246 | % Is point z in region? 247 | if isempty(R.outerboundary) 248 | outin = true; 249 | else 250 | outin = isinside(R.outerboundary{1}, z); 251 | for k = 2:numel(R.outerboundary) 252 | outin = outin & isinside(R.outerboundary{k}, z); 253 | end 254 | end 255 | 256 | if isempty(R.innerboundary) 257 | inin = true; 258 | else 259 | inin = ~isinside(R.innerboundary{1}, z); 260 | for k = 2:numel(R.innerboundary) 261 | inin = inin & ~isinside(R.innerboundary{k}, z); 262 | end 263 | end 264 | 265 | tf = outin & inin; 266 | end 267 | 268 | function tf = isinterior(R) 269 | % True if region has only outer boundaries. False otherwise. 270 | tf = hasouter(R) & ~hasinner(R); 271 | end 272 | 273 | function tf = isinside(R, z) 274 | % Another name for isin(R, z). 275 | tf = isin(R, z); 276 | end 277 | 278 | function tf = issimplyconnected(R) 279 | % True if region is simply connected (only one outer or inner boundary, but 280 | % not both). 281 | tf = (isinterior(R) & R.numouter == 1) ... 282 | | (isexterior(R) & R.numinner == 1); 283 | end 284 | 285 | function b = outer(R) 286 | if R.numouter == 1 287 | b = R.outerboundary{1}; 288 | else 289 | b = R.outerboundary; 290 | end 291 | end 292 | 293 | function out = plot(r, varargin) 294 | % Plot region without fill. 295 | 296 | newplot 297 | washold = ishold; 298 | hold on 299 | 300 | btag = sprintf('boundary_%s', num2hex(rand)); 301 | inner = r.innerboundary; 302 | for k = 1:numel(inner) 303 | plot(inner{k}, varargin{:}, 'tag', btag) 304 | end 305 | outer = r.outerboundary; 306 | for k = 1:numel(outer) 307 | plot(outer{k}, varargin{:}, 'tag', btag) 308 | end 309 | 310 | if ~washold 311 | hold off 312 | axis(plotbox(r)) 313 | aspectequal 314 | end 315 | 316 | if nargout 317 | out = findobj(gca, 'tag', btag); 318 | end 319 | end 320 | 321 | function box = plotbox(R, scale) 322 | if nargin < 2 323 | scale = []; 324 | end 325 | 326 | if isempty(R) 327 | box = []; 328 | return 329 | end 330 | 331 | box = cmt.plotbox(cmt.bb2z(boundbox(R)), scale); 332 | end 333 | end 334 | 335 | methods(Static, Hidden) 336 | function cc = checkcc(suitor) 337 | % Transform suitor to column cell of closedcurves with verification. 338 | if numel(suitor) == 1 && ~isa(suitor, 'cell') 339 | suitor = {suitor}; 340 | end 341 | for k = 1:numel(suitor) 342 | if ~isa(suitor{k}, 'closedcurve') 343 | error('Argument to region must be a closedcurve or cell array thereof.') 344 | end 345 | end 346 | cc = suitor(:); 347 | end % checkcc 348 | end 349 | 350 | end 351 | -------------------------------------------------------------------------------- /spec_mobius_grids.m: -------------------------------------------------------------------------------- 1 | %% Specification by example -- Mobius transformations. 2 | % This file best viewed via the command 3 | % 4 | % open(publish('spec_mobius_grids')). 5 | % 6 | clear 7 | 8 | 9 | %% 10 | % Create a basic Mobius transformation. 11 | z3 = [1, 1i, -1]; 12 | w3 = [-1, -2i, 0]; 13 | m1 = mobius(z3, w3); 14 | 15 | %% 16 | % The Mobius transform is a conformal map. 17 | if isa(m1, 'conformalmap') 18 | disp('m1 is a conformal map.') 19 | else 20 | disp('m1 is not a conformal map.') 21 | end 22 | 23 | %% 24 | % Plotting a conformal map gives an image in the range of a grid in the 25 | % domain. 26 | plot(m1) 27 | 28 | %% 29 | % Let's see that again in slow motion. 30 | % (NOTE: 31 | % Technically a Mobius map is an entire function, but for visualization 32 | % convenience it uses the 3-vectors it was given on construction to define 33 | % circles for its domain and range.) 34 | gd = grid(domain(m1)); 35 | clf, hold on 36 | plot(m1(gd), 'color', cmtplot.grey) 37 | plot(range(m1), 'color', 'k') 38 | axis(plotbox(range(m1))) 39 | aspectequal 40 | axis off 41 | 42 | 43 | %% 44 | % Another transformation for a composition example. 45 | s3 = [0, 3, 1i]; 46 | m2 = mobius(w3, s3); 47 | 48 | %% 49 | % The |*| operator is the composition operator for conformal maps, with 50 | % each map applied in the standard order. The following creates a new map 51 | % which applies |m1|, then |m2|. Internally Mobius multiplies the matrices for 52 | % |m1| and |m2| and creates a new Mobius object with the resulting matrix. 53 | % This explains the discrepancy with the result of the example below. 54 | m = m2*m1; 55 | fprintf('composition comparison: %.g\n', norm(m2(m1(z3)).' - m(z3).')) 56 | 57 | %% 58 | % The base class has composition constructor behavior. Given arguments of the 59 | % form (map1, map2, ..., mapn), it creates a new map via the composition 60 | % |mapn * ... * map2 * map1|. 61 | % Internally |conformalmap| stores an array of the given maps and applies 62 | % each in order. 63 | mf = conformalmap(m1, m2); 64 | fprintf('other comparison: %.g\n', norm(m2(m1(z3)).' - mf(z3).')) 65 | -------------------------------------------------------------------------------- /spec_splinep.m: -------------------------------------------------------------------------------- 1 | %% Specification by example -- periodic spline. 2 | % This file best viewed via the command 3 | % 4 | % open(publish('spec_splinep')). 5 | % 6 | clear 7 | 8 | %% 9 | % One way to create a spline object is to give a set of knots in the plane. 10 | s = splinep([ ... 11 | 0.5896 + 1.2486i; -0.1426 + 1.5954i; -0.9133 + 1.1561i 12 | -0.8465 + 0.3536i; -1.1116 - 0.2398i; -1.2695 - 0.9643i 13 | -0.5660 - 1.1075i; 0.2013 - 0.7552i; 0.8362 - 0.9634i 14 | 1.5838 - 0.7013i; 1.3141 + 0.4008i; 0.8474 + 0.7291i ... 15 | ]); 16 | plot(s) 17 | hold on 18 | plot(s.zpts, 'rd') % plot the knots 19 | 20 | %% 21 | % If you are not into tediously entering knot points by hand, you may use 22 | % the command (note the empty array as the only argument) 23 | % 24 | % s = splinep([]); 25 | % 26 | % which will start an interactive figure in which you choose the knot 27 | % points with the mouse button. Press the < enter > key after selecting the 28 | % last point, or use the right mouse button to select the last point. You 29 | % can then use the command 30 | % 31 | % replicate(s) 32 | % 33 | % to print something you can copy and paste into a script. As was done with 34 | % the given set of knots above. You should try it! 35 | 36 | 37 | %% 38 | % Splines, or rather their knots, may be subjected to arbitrary affine 39 | % transformations. 40 | clf 41 | subplot(1,2,1) 42 | plot(50*exp(1i*pi/2)*s) 43 | subplot(1,2,2) 44 | plot(s + 70 - 25i); 45 | 46 | 47 | %% 48 | % Splines may even be subjected to Mobius transformations. (You should 49 | % probably be VERY careful with this.) 50 | m = mobius([2, 2i, -2], [-1, -2i, 0]); 51 | clf 52 | plot(m(s)) 53 | -------------------------------------------------------------------------------- /spec_splinepwp.m: -------------------------------------------------------------------------------- 1 | %% Specification by example -- periodic piecewise spline. 2 | % This file best viewed via the command 3 | % 4 | % open(publish('spec_splinepwp')). 5 | % 6 | clear 7 | 8 | 9 | %% 10 | % Some sample knots to get us started. We'll show a regular periodic 11 | % spline with its knots numbered, and then add some corners. 12 | 13 | knots = [1, 1i, -1, -1i]; 14 | G = splinep(knots); 15 | 16 | plot(G), hold on 17 | plot(knots, 'b.', 'markersize', 18) 18 | for k = 1:numel(knots) 19 | text(real(knots(k)), imag(knots(k)), [' ' int2str(k)]) 20 | end 21 | axis off, hold off 22 | 23 | 24 | %% 25 | % Corners are easy to specify. 26 | 27 | corners = [1, 2]; 28 | G = splinepwp(knots, corners); 29 | 30 | plot(G), hold on 31 | plot(knots, 'b.', 'markersize', 18) 32 | for k = 1:numel(knots) 33 | text(real(knots(k)), imag(knots(k)), [' ' int2str(k)]) 34 | end 35 | axis off, hold off 36 | 37 | 38 | %% 39 | % Note that |splinepwp| requires a corner be the first knot, so the 40 | % constructor will rearrange the knots as needed to make this happen. 41 | 42 | corners = [3, 4]; 43 | G = splinepwp(knots, corners); 44 | 45 | plot(G), hold on 46 | plot(knots, 'b.', 'markersize', 18) 47 | for k = 1:numel(G.knots) 48 | text(real(G.knots(k)), imag(G.knots(k)), [' ' int2str(k)]) 49 | end 50 | axis off, hold off 51 | 52 | 53 | %% 54 | % Interestingly, if the region isn't too "strange", the Szego kernel will 55 | % give a map with boundary corners. 56 | 57 | f = szmap(G, 0); 58 | plot(f) 59 | -------------------------------------------------------------------------------- /spec_szego.m: -------------------------------------------------------------------------------- 1 | %% Specification by example -- Szego kernel map. 2 | % This file best viewed via the command 3 | % 4 | % open(publish('spec_mobius_grids')). 5 | % 6 | clear 7 | 8 | %% 9 | % A spline to work with. Although technically the Szego kernel represents 10 | % an operator on L^2, the code only works well for now on a closed curve 11 | % that thinks it is C^2. 12 | G = splinep([ ... 13 | 0.2398 + 0.6023i; 0.3567 + 1.0819i; 0.2632 + 1.5965i 14 | -0.5205 + 1.7485i; -1.0585 + 1.1170i; -1.0702 + 0.5088i 15 | -0.5906 + 0.0994i; -0.7778 - 0.4269i; -1.2924 - 0.6140i 16 | -1.4561 - 1.2456i; -0.5439 - 1.3509i; 0.2515 - 1.0702i 17 | 0.3099 - 0.6023i; 0.7427 - 0.5906i; 1.1053 - 0.1813i 18 | 1.2807 + 0.3567i ... 19 | ]); 20 | 21 | %% 22 | % Make the map and draw it. 23 | f = szmap(G, 0); 24 | plot(f) 25 | 26 | %% 27 | % So how is this done? Start by computing the Szego kernel for |G|. 28 | S = szego(G, 0); 29 | disp(S) 30 | 31 | 32 | %% 33 | % Take some evenly spaced points around G, and consider the boundary 34 | % correspondence. 35 | t = (0:19)'/20; 36 | 37 | clf 38 | subplot(1,2,1) 39 | plot(G), hold on, plot(G(t), 'rd') 40 | axis off 41 | subplot(1,2,2) 42 | plot(circle(0, 1)), hold on, plot(exp(1i*theta(S, t)), 'rd') 43 | axis off 44 | 45 | %% 46 | % Now consider the inverse boundary correspondence. 47 | 48 | clf 49 | subplot(1,2,1) 50 | plot(circle(0, 1)), hold on, plot(exp(2i*pi*t), 'rd') 51 | axis off 52 | subplot(1,2,2) 53 | plot(G), hold on, plot(G(invtheta(S, 2*pi*t)), 'rd') 54 | axis off 55 | 56 | %% 57 | % Using this inverse boundary correspondence and an FFT, create a conformal 58 | % map. 59 | N = 512; 60 | th = 2*pi*(0:N-1)'/N; 61 | t = invtheta(S, th); 62 | w = G(t); 63 | c = fft(w)/N; 64 | f = @(z) polyval(flipud(c), z); 65 | 66 | %% 67 | % Aaaaaand draw. 68 | gd = grid(unitdisk); 69 | gdi = cell(1, numel(gd)); 70 | for k = 1:numel(gd) 71 | gdi{k} = f(gd(k)); 72 | end 73 | gdi = gridcurves(gdi); 74 | 75 | subplot(1,2,1) 76 | plot(gd) 77 | subplot(1,2,2) 78 | plot(gdi) 79 | 80 | 81 | %% 82 | % Now we'll do a shape fingerprint. Let's use a new shape. 83 | clear 84 | G = splinep([ ... 85 | 0.8129 + 0.0643i; 1.1871 + 0.5673i; 1.0468 + 1.3275i 86 | 0.2865 + 1.6316i; -0.0643 + 1.2339i; -0.4035 + 0.6257i 87 | -1.0117 + 0.8480i; -1.4211 + 0.5789i; -1.4795 - 0.2982i 88 | -1.1871 - 1.1520i; -0.2865 - 0.5205i; 0.4503 - 0.7076i ... 89 | ]); 90 | 91 | 92 | %% 93 | % Create a map for the interior and exterior of the curve. We have 94 | % overloaded the conjugate transpose operator (|'|) for |szmap| to give the 95 | % exterior map of a simply connected region given the interior map. 96 | 97 | fin = szmap(G, 0); 98 | fout = fin'; 99 | 100 | 101 | %% 102 | % Justification for the use of the conjugate transpose operator comes from 103 | % the way the exterior map is composed. The command |fout = fin'| is in 104 | % spirit composed of the commands 105 | % 106 | % g = szmap(G', 0); 107 | % fout = @(z) 1./conj(g(1./conj(z))); 108 | % 109 | % Overloading the |ctranspose| operator allows us to also specify the 110 | % proper domain and range for the new map, instead of just having a 111 | % function handle, which in turn allows using the automated mechanisms for 112 | % plotting, and allowing us to treat |fout| as a |szmap| object. 113 | 114 | clf 115 | plot(fin) 116 | hold on 117 | plot(fout) 118 | plot(G(0), 'r.', 'markersize', 18) 119 | 120 | 121 | %% 122 | % Compute the shape fingerprint, which is just the graph of the boundary 123 | % correspondence angles between the shape and the unit disk with respect to the 124 | % interior and exterior maps. 125 | Sin = kernel(fin); 126 | Sout = kernel(fout); 127 | s = (0:199)'/200; % Points in [0, 1) for the 128 | % parameterized boundary G. 129 | tin = unwrap(theta(Sin, s)); % Interior boundary image angles for the 130 | % map |G -> disk|. 131 | tout = unwrap(theta(Sout, s)); % Exterior boundary image angles. 132 | 133 | 134 | %% 135 | clf 136 | plot(tin/pi, tout/pi) 137 | xlabel('\theta_{inner}/\pi') 138 | ylabel('\theta_{outer}/\pi') 139 | axis([0 2 0 2]) 140 | grid on 141 | aspectequal 142 | -------------------------------------------------------------------------------- /splinep.m: -------------------------------------------------------------------------------- 1 | classdef splinep < closedcurve 2 | % SPLINEP class represents a periodic spline in the plane. 3 | 4 | % This file is a part of the CMToolkit. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | properties 12 | knotX % knot x-coordinates 13 | knotY % knot y-coordinates 14 | 15 | ppArray % piecewise polynomial array 16 | chordalArclength = 0 % total chordal arclength 17 | end 18 | 19 | properties(Dependent) 20 | xpts 21 | ypts 22 | zpts 23 | end 24 | 25 | methods 26 | function S = splinep(varargin) 27 | if ~nargin 28 | return 29 | end 30 | 31 | needpts = false; 32 | tofignum = []; 33 | 34 | switch nargin 35 | case 1 36 | tmp = varargin{1}; 37 | if isa(varargin{1}, 'splinep') 38 | xk = tmp.knotX; 39 | yk = tmp.knotY; 40 | elseif isempty(tmp) || (numel(tmp) == 1 && cmtplot.isFigHandle(tmp)) 41 | % Need to prompt for points. 42 | needpts = true; 43 | tofignum = tmp; 44 | else 45 | % Assume it's a complex vector. 46 | xk = real(tmp(:)); 47 | yk = imag(tmp(:)); 48 | end 49 | 50 | case 2 51 | % Just assume it's two real vectors. 52 | xk = varargin{1}(:); 53 | yk = varargin{2}(:); 54 | if numel(xk) ~= numel(yk) 55 | error('CMT:InvalidArgument', ... 56 | 'Input vectors must have the same number of elements.') 57 | end 58 | 59 | otherwise 60 | error('CMT:InvalidArgument', ... 61 | 'Expected a complex vector or two real vectors.') 62 | end 63 | 64 | if needpts 65 | [xk, yk] = splinep.getPts(tofignum); 66 | end 67 | 68 | if xk(1) ~= xk(end) 69 | xk = [xk; xk(1)]; 70 | yk = [yk; yk(1)]; 71 | end 72 | 73 | % Superclass constructor here. 74 | 75 | % Spline data. 76 | S.knotX = xk; 77 | S.knotY = yk; 78 | [S.ppArray, S.chordalArclength] = splinep.makeSpline(xk, yk); 79 | end 80 | 81 | function out = apply(S, op) 82 | % Apply operator to spline knots. 83 | switch class(op) 84 | case 'mobius' 85 | M = matrix(op); 86 | z = S.zpts; 87 | out = splinep((M(1)*z + M(3))./(M(2)*z + M(4))); 88 | 89 | otherwise 90 | error('CMT:NotDefined', 'Application of %s to %s is not defined.', ... 91 | class(op), class(S)) 92 | end 93 | end 94 | 95 | function L = arclength(S) 96 | L = S.chordalArclength; 97 | end 98 | 99 | function disp(S) 100 | fprintf('splinep object:\n\n') 101 | fprintf(' defined with %d spline knots,\n', numel(S.knotX)) 102 | lstr = strtrim(evalc('disp(arclength(S))')); 103 | fprintf(' total chordal arc length %s\n\n', lstr) 104 | end 105 | 106 | function x = get.xpts(S) 107 | x = S.knotX; 108 | end 109 | 110 | function y = get.ypts(S) 111 | y = S.knotY; 112 | end 113 | 114 | function z = get.zpts(S) 115 | z = complex(S.knotX, S.knotY); 116 | end 117 | 118 | function S = minus(S, z) 119 | S = plus(S, -z); 120 | end 121 | 122 | function S = mrdivide(S, z) 123 | if isa(z, 'splinep') 124 | [z, S] = deal(S, z); 125 | end 126 | if ~(isa(z, 'double') && numel(z) == 1) 127 | error('CMT:NotDefined', ... 128 | 'Only scalar division allowed.') 129 | end 130 | S = mtimes(S, 1/z); 131 | end 132 | 133 | function S = mtimes(S, z) 134 | % Scalar multiplication. 135 | if ~isa(S, 'splinep') 136 | [z, S] = deal(S, z); 137 | end 138 | if isa(z, 'double') && numel(z) == 1 139 | S = splinep(z*S.zpts); 140 | else 141 | error('CMT:NotDefined', ... 142 | 'Only scalar multiplication defined.') 143 | end 144 | end 145 | 146 | function S = plus(S, z) 147 | % Translate a spline. 148 | if isa(z, 'splinep') 149 | [z, S] = deal(S, z); 150 | end 151 | if ~(isa(z, 'double') && numel(z) == 1) 152 | error('CMT:NotDefined', ... 153 | 'Only translation by a scalar allowed.') 154 | end 155 | S = splinep(S.zpts + z); 156 | end 157 | 158 | function z = point(S, t) 159 | t = modparam(S, t)*S.chordalArclength; 160 | z = complex(ppval(S.ppArray{1,1}, t), ... 161 | ppval(S.ppArray{2,1}, t)); 162 | end 163 | 164 | function replicate(S) 165 | % Print in format for pasting into scripts, etc. 166 | fprintf('%s = splinep([ ...\n ', inputname(1)); 167 | z = S.zpts; 168 | n = numel(z) - 1; % don't repeat first point 169 | for k = 1:n 170 | fprintf('%.4f', real(z(k))); 171 | if imag(z(k)) < 0 172 | fprintf(' - '); 173 | else 174 | fprintf(' + '); 175 | end 176 | fprintf('%.4fi', abs(imag(z(k)))); 177 | 178 | if k ~= n 179 | if mod(k, 3) 180 | fprintf('; '); 181 | else 182 | fprintf('\n '); 183 | end 184 | end 185 | end 186 | 187 | fprintf(' ...\n]);\n'); 188 | end 189 | 190 | function z2 = second(S, t) 191 | t = modparam(S, t)*S.chordalArclength; 192 | z2 = complex(ppval(S.ppArray{1,3}, t), ... 193 | ppval(S.ppArray{2,3}, t))*arclength(S)^2; 194 | end 195 | 196 | function zt = tangent(S, t) 197 | t = modparam(S, t)*arclength(S); 198 | zt = complex(ppval(S.ppArray{1,2}, t), ... 199 | ppval(S.ppArray{2,2}, t))*arclength(S); 200 | end 201 | 202 | function S = uminus(S) 203 | S = splinep(-S.zpts); 204 | end 205 | end 206 | 207 | methods(Access=protected) 208 | function h = dumbPlot(S, varargin) 209 | t = (0:200)'/200; 210 | h = plot(point(S, t), varargin{:}); 211 | end 212 | end 213 | 214 | methods(Access=protected, Static) 215 | function [x, y] = getPts(tofignum) 216 | fprintf(['\n' ... 217 | ' Left mouse button picks points.\n' ... 218 | ' Right mouse button picks last point,\n' ... 219 | ' or ends selection.\n' ... 220 | ' Point selection order determines curve\n' ... 221 | ' orientation.\n\n']); 222 | 223 | if isempty(tofignum) 224 | figure; 225 | axis(2*[-1 1 -1 1]); 226 | set(gca, 'dataaspectratio', [1 1 1]); 227 | else 228 | figure(tofignum); 229 | end 230 | hold on 231 | grid on 232 | box on 233 | 234 | x = []; 235 | y = []; 236 | sh = []; 237 | np = 0; 238 | button = 1; 239 | while button == 1 240 | [xi, yi, button] = ginput(1); 241 | if isempty(button) 242 | % return pressed 243 | break 244 | end 245 | plot(xi, yi, 'bo'); 246 | np = np + 1; 247 | x(np) = xi; %#ok 248 | y(np) = yi; %#ok 249 | 250 | if ~isempty(sh) 251 | delete(sh); 252 | end 253 | if numel(x) > 1 254 | sh = dumbPlot(splinep(x, y), 'k'); 255 | end 256 | text(xi, yi, [' ' int2str(np)]); 257 | end 258 | 259 | x = [x(:); x(1)]; 260 | y = [y(:); y(1)]; 261 | end 262 | 263 | function [pp, tl] = makeSpline(x, y) 264 | % This algorithm is from " PERIODIC CUBIC SPLINE INTERPOLATION USING 265 | % PARAMETRIC SPLINES" by W.D. Hoskins and P.R. King, Algorithm 73, The 266 | % Computer Journal, 15, 3(1972) P282-283. Fits a parametric periodic 267 | % cubic spline through n1 points (x(i), y(i)) (i = 1, ... ,n1) with 268 | % x(1) = x(n1) and y(1) = y(n1). This function returns the first three 269 | % derivatives of x and y, the chordal distances h(i) of (x(i),y(i)) and 270 | % (x(i + 1), y(i + 1)) (i = 1, ..., n1 - 1) with h(n1) = h(1) and the 271 | % total distance. 272 | % 273 | % See also INTERP_1, INTERP_2, MATLAB function SPLINE. 274 | 275 | % Thomas K. DeLillo, Lianju Wang 07-05-99. 276 | % modified a bit by E. Kropf, 2013, 2014. 277 | 278 | if abs(x(1) - x(end)) > 100*eps || abs(y(1) - y(end)) > 100*eps 279 | x(end+1) = x(1); 280 | y(end+1) = y(1); 281 | end 282 | nk = numel(x); 283 | n = nk - 1; 284 | dx = diff(x); 285 | dy = diff(y); 286 | h = sqrt(dx.^2 + dy.^2); 287 | tl = sum(h); 288 | h(nk) = h(1); 289 | p = h(1:n); 290 | q = h(2:nk); 291 | a = q./(p + q); 292 | b = 1 - a; 293 | c = spdiags(... 294 | [ [b(n);ones(n-1,1)] [a(2:n);0] 2*ones(n,1) [0;b(1:n-1)] ... 295 | [ones(n-1,1);a(1)] ], ... 296 | [-n+1 -1 0 1 n-1], n, n); 297 | d1 = 3*(a.*dx./p + b.*[dx(2:n); x(2) - x(nk)]./q); 298 | mmdflag = spparms('autommd'); 299 | spparms('autommd', 0); 300 | x1 = c\d1; 301 | spparms('autommd', mmdflag); 302 | x1(2:nk) = x1; 303 | x1(1) = x1(nk); 304 | d = 3*(a.*dy./p + b.*[dy(2:n); y(2) - y(nk)]./q); 305 | mmdflag = spparms('autommd'); 306 | spparms('autommd', 0); 307 | y1 = c\d; 308 | spparms('autommd', mmdflag); 309 | y1(2:nk) = y1; 310 | y1(1) = y1(nk); 311 | x2(2:nk) = 2*(x1(1:n) + 2*x1(2:nk) - 3*dx./p)./p; 312 | y2(2:nk) = 2*(y1(1:n) + 2*y1(2:nk) - 3*dy./p)./p; 313 | x2(1) = x2(nk); 314 | y2(1) = y2(nk); 315 | x2 = x2'; 316 | y2 = y2'; 317 | x3 = diff(x2)./p; 318 | x3(nk) = x3(1); 319 | y3 = diff(y2)./p; 320 | y3(nk) = y3(1); 321 | 322 | % Make pp for later evaluation. 323 | pp = cell(2, 3); 324 | t = [0; cumsum(h)]; 325 | 326 | pp{1,1} = mkpp(t, [x3/6, x2/2, x1, x]); 327 | pp{2,1} = mkpp(t, [y3/6, y2/2, y1, y]); 328 | for j = 1:2 329 | coef = pp{j,1}.coefs; 330 | pp{j,2} = mkpp(t, [3*coef(:,1), 2*coef(:,2), coef(:,3)]); 331 | pp{j,3} = mkpp(t, [6*coef(:,1), 2*coef(:,2)]); 332 | end 333 | end 334 | end 335 | 336 | end 337 | -------------------------------------------------------------------------------- /splinepwp.m: -------------------------------------------------------------------------------- 1 | classdef splinepwp < closedcurve 2 | % SPLINEPWP is a periodic piecewise spline with corners. 3 | % 4 | % G = splinepwp(knots, corners) 5 | % The corners array is a set of indicies of the knots array which dictates 6 | % which knots in the list will be corners. The array corners should be 7 | % strictly monotonically increasing with 8 | % 1 <= corners(1) and corners(end) <= numel(knots). 9 | % If corners(1) ~= 1, then the knots and corners arrays will be rearranged 10 | % so that this is the case. 11 | % 12 | % Currently the corner angles are determined strictly by the angles of 13 | % neighboring knots, by pretending we are making a polygon with the knots 14 | % as vertices. 15 | % 16 | % This class is mainly for testing corner cases on boundaries that aren't 17 | % polygons. For instance it is completely arbitrary that the value of the 18 | % tangent at a corner is the average of the incoming and outgoing tangent 19 | % vectors. In fact to create the splines between corners, the endpoint 20 | % tangent vectors arbitrarily have a magnitude of 5, which was selected 21 | % because it gives the "nicest, natural" distribution of points for an 22 | % evenly spaced discrete parameterization. 23 | 24 | % This file is a part of the CMToolkit. 25 | % It is licensed under the BSD 3-clause license. 26 | % (See LICENSE.) 27 | 28 | % Copyright Toby Driscoll, 2014. 29 | % Written by Everett Kropf, 2014. 30 | 31 | properties(SetAccess=protected) 32 | knots % Complex array of spline knots. 33 | corners % Vector of index values for knots indicating corners. 34 | alpha % Array of calculated corner interior angles. 35 | tknots % Parameter values for knots. 36 | cornerTangent % Incoming/outgoing corner tangent vectors. 37 | end 38 | 39 | properties(Dependent) 40 | breaks 41 | end 42 | 43 | properties(Access=protected) 44 | ppx 45 | ppy 46 | end 47 | 48 | methods 49 | function G = splinepwp(knots, corners) 50 | % Constructor. 51 | 52 | if ~nargin 53 | return 54 | end 55 | 56 | % Check input. 57 | corners = corners(:); 58 | knots = knots(:); 59 | if knots(1) == knots(end) 60 | knots = knots(1:end-1); 61 | end 62 | if any(diff(corners) <= 0) 63 | error('CMT:BadArguemnt', ... 64 | 'The corners array must be strictly monotonically increasing.') 65 | end 66 | try 67 | knots(corners); 68 | catch err 69 | if strcmp(err.identifier, 'MATLAB:badsubscript') 70 | error('CMT:BadArgument', ... 71 | 'The corners array must be a valid set of indices for knots.') 72 | else 73 | rethrow(err) 74 | end 75 | end 76 | 77 | % Constructor work. 78 | G.knots = knots; 79 | G.corners = corners; 80 | G = calculateSplines(G); 81 | end 82 | 83 | function tk = get.breaks(G) 84 | tk = [G.tknots(G.corners); 1]; 85 | end 86 | 87 | function z = point(G, t) 88 | % Point on curve at t. 89 | 90 | z = paramEval(G, t, 0); 91 | end 92 | 93 | function zt = tangent(G, t) 94 | % Tangent to curve at t. With average corner tangents. 95 | 96 | zt = paramEval(G, t, 1); 97 | cavg = sum(G.cornerTangent, 2)/2; 98 | ct = G.breaks; 99 | for k = 1:numel(ct)-1 100 | ctL = abs(t - ct(k)) < 10*eps; 101 | zt(ctL) = cavg(k); 102 | end 103 | end 104 | end 105 | 106 | methods(Access=protected) 107 | function G = calculateSplines(G) 108 | v = G.knots; 109 | cv = G.corners; 110 | if cv(1) ~= 1 111 | % Shift corners and knots. 112 | shift = cv(1) - 1; 113 | v = circshift(v, -shift); 114 | cv = cv - shift; 115 | end 116 | 117 | % Pseudo arc length parameterization via chordal arclengths. 118 | nv = numel(v); 119 | v = [v; v(1)]; 120 | dL = abs(diff(v)); 121 | t = [0; cumsum(dL)]; 122 | t = t/t(end); 123 | 124 | % Corner angles determined by neighboring knots. 125 | pv = mod(cv - 2, nv) + 1; 126 | fv = mod(cv, nv) + 1; 127 | alpha_ = mod(angle((v(pv) - v(cv))./(v(fv) - v(cv)))/pi, 2); 128 | 129 | % Outgoing/incoming tangent vectors. 130 | vtan = [ v(fv) - v(cv), v(cv) - v(pv) ]; 131 | mu = 5; % (arbitrary) tangent vector magnitude 132 | vtan = mu*vtan./abs(vtan); 133 | 134 | % Compute piecewise polynomial splines for segments between corners. 135 | ncv = numel(cv); 136 | ppx_(ncv, 3) = mkpp([1 1], [1 1]); 137 | ppy_ = ppx_; 138 | for k = 1:ncv 139 | if k + 1 <= ncv 140 | vdx = cv(k):cv(k+1); 141 | else 142 | vdx = cv(k):nv+1; 143 | end 144 | vex = [vtan(k,1); v(vdx); vtan(mod(k, ncv)+1,2)]; 145 | tk = t(vdx); 146 | 147 | % Piecewise polynomials and derivatives. 148 | ppx_(k,1) = spline(tk, real(vex)); 149 | ppx_(k,2:3) = splinepwp.ppderivs(ppx_(k,1)); 150 | ppy_(k,1) = spline(tk, imag(vex)); 151 | ppy_(k,2:3) = splinepwp.ppderivs(ppy_(k,1)); 152 | end 153 | 154 | G.knots = v(1:end-1); 155 | G.corners = cv; 156 | G.alpha = alpha_; 157 | G.ppx = ppx_; 158 | G.ppy = ppy_; 159 | G.tknots = t; 160 | G.cornerTangent = vtan; 161 | end 162 | 163 | function z = paramEval(G, t, d) 164 | % Evaluate curve given parameter using derivative level d. 165 | 166 | if nargin < 3 167 | % No derivative. 168 | d = 0; 169 | end 170 | d = d + 1; 171 | t = modparam(G, t); 172 | z = nan(size(t)); 173 | 174 | brks = G.breaks; 175 | for k = 1:numel(G.corners) 176 | tL = brks(k) <= t & t < brks(k+1); 177 | z(tL) = complex(ppval(G.ppx(k,d), t(tL)), ppval(G.ppy(k,d), t(tL))); 178 | end 179 | end 180 | end 181 | 182 | methods(Static, Access=private) 183 | function ppd = ppderivs(pp) 184 | % First and second derivatives of piecewise polynomial pp. 185 | coef = pp.coefs; 186 | ppd = [ mkpp(pp.breaks, [3*coef(:,1), 2*coef(:,2), coef(:,3)]), ... 187 | mkpp(pp.breaks, [6*coef(:,1), 2*coef(:,2)]) ]; 188 | end 189 | end 190 | 191 | end 192 | -------------------------------------------------------------------------------- /szego.m: -------------------------------------------------------------------------------- 1 | classdef szego < cmtobject 2 | % SZEGO class represents a Szego kernel. 3 | % 4 | % S = szego(curve, a) 5 | % curve - closedcurve object 6 | % a - point such that f(a) = 0 where f is map to disk. 7 | % 8 | % szego(curve, a, 'name', value, ...) 9 | % Uses preferences specified by name/value pairs. See szset for valid 10 | % name/value pairs. 11 | % 12 | % s = theta(S, t) - boundary correspondence function gives angle on the unit 13 | % circle of the image of a point on curve given by parameter t, 14 | % th(t) = angle(-1i*phi(t).^2.*tangent(curve, t)), 15 | % normalized so that th(0) = 0. 16 | % 17 | % sp = thetap(S, t) - derivative of th(t), 18 | % thp(t) = 2*pi/S(a,a)*abs(phi(t).^2) 19 | % 20 | % t = invtheta(S, s, tol) - inverse of theta via a Newton method with automatic 21 | % initial guess. Input s is normalized to [0,2*pi), and default tolerance 22 | % tol is 1e-12. 23 | % 24 | % v = phi(S, t) - Szego kernel interpolation 25 | % phi(t) := sqrt(abs(tangent(curve, t))).*S(point(curve, t), a) 26 | % where S(z, a) is the computed Szego kernel. 27 | % 28 | % Trummer, M. "An efficient implementation of a conformal mapping method based 29 | % on the Szego kernel." SIAM J. Numer. Anal., 23(4), 1986. 30 | % 31 | % See also szset. 32 | 33 | % This file is a part of the CMToolkit. 34 | % It is licensed under the BSD 3-clause license. 35 | % (See LICENSE.) 36 | 37 | % Copyright Toby Driscoll, 2014. 38 | % Written by Everett Kropf, 2014. 39 | 40 | properties 41 | curve % Closed curve where kernel is defined. 42 | confCenter = 0 % Point that goes to origin under Riemann map. 43 | numCollPts % Number of collocation points. 44 | 45 | phiColl % Collcation phi values. 46 | theta0 % Rotation constant. 47 | Saa % Szego kernel value S(a,a). 48 | relResid % Collocation solution relative residual. 49 | 50 | zPts % Stored collocation points. 51 | zTan % Stored collocation tangents. 52 | zUnitTan % Stored collocation unit tangents. 53 | dtColl = 0 % Collocation differential. 54 | 55 | newtTol % Newton iteration tolerance. 56 | beNoisy = false % Logical; informational output? 57 | end 58 | 59 | methods 60 | function S = szego(curve, varargin) 61 | % Constructor. 62 | 63 | % Make sure defaults are set. 64 | opts = get(S, szset); 65 | 66 | if ~nargin 67 | return 68 | end 69 | 70 | if ~isa(curve, 'closedcurve') 71 | error('CMT:InvalidArgument', 'Expected a closedcurve object.') 72 | end 73 | 74 | if nargin > 2 75 | opts = set(opts, varargin{:}); 76 | end 77 | 78 | a = opts.confCenter; 79 | kerndat = szego.compute_kernel(curve, a, opts); 80 | knames = fieldnames(kerndat); 81 | for k = 1:numel(knames) 82 | S.(knames{k}) = kerndat.(knames{k}); 83 | end 84 | 85 | S.curve = curve; 86 | S.confCenter = a; 87 | S.numCollPts = opts.numCollPts; 88 | S.theta0 = angle(-1i*phi(S, 0)^2*tangent(S.curve, 0)); 89 | S.Saa = sum(abs(S.phiColl.^2))*S.dtColl; 90 | S.newtTol = opts.newtonTol; 91 | S.beNoisy = opts.trace; 92 | end 93 | 94 | function disp(S) 95 | fprintf('Szego kernel object:\n\n') 96 | astr = strtrim(evalc('disp(S.confCenter)')); 97 | fprintf('with a = %s,\n', astr) 98 | resstr = strtrim(evalc('disp(S.relResid)')); 99 | fprintf('and kernel solution relative residual\n\t%s,\n', resstr) 100 | fprintf('computed over curve\n') 101 | disp(S.curve) 102 | end 103 | 104 | function A = kerz_stein(S, t) 105 | % Calculate Kerzmann-Stein kernel. 106 | t = t(:); 107 | w = point(S.curve, t); 108 | wt = tangent(S.curve, t); 109 | wT = wt./abs(wt); 110 | 111 | z = S.zPts; 112 | zt = S.zTan; 113 | zT = S.zUnitTan; 114 | separation = 10*eps(max(abs(z))); 115 | 116 | function A = KS_by_idx(wi, zi) 117 | z_w = z(zi).' - w(wi); 118 | A = sqrt(abs(wt(wi).*zt(zi).'))/(2i*pi).* ... 119 | (conj(wT(wi)./z_w) - zT(zi).'./z_w); 120 | A(abs(z_w) < separation) = 0; 121 | end 122 | % Function bsxfun will call KS_by_idx with single wi and a vector of zi. 123 | % Column vector w and row vector z determines shape of resulting 124 | % matrix A. 125 | A = bsxfun(@KS_by_idx, (1:numel(w))', 1:S.numCollPts); 126 | end 127 | 128 | function v = phi(S, t) 129 | % Calculate scaled Szego kernel. 130 | v = psi(S, t) - kerz_stein(S, t)*S.phiColl*S.dtColl; 131 | end 132 | 133 | function y = psi(S, t) 134 | % Calculate integral equation RHS. 135 | wt = tangent(S.curve, t(:)); 136 | y = 1i/(2*pi)./sqrt(abs(wt)) .* ... 137 | conj(wt./(point(S.curve, t(:)) - S.confCenter)); 138 | end 139 | 140 | function th = theta(S, t) 141 | % Give unit circle correspondence angle. 142 | th = angle(-1i.*phi(S, t).^2.*tangent(S.curve, t(:))) - S.theta0; 143 | % KLUDGE: Roundoff allows theta(S, 0) ~= 0. "Fix" this. 144 | th(t == 0) = 0; 145 | end 146 | 147 | function [t, output] = invtheta(S, s, tol) 148 | % Calculate inverse of theta by bisection/Newton method. 149 | 150 | % This should really be more modularised. -- EK 151 | 152 | if nargin < 3 || isempty(tol) 153 | ntol = S.newtTol; 154 | else 155 | ntol = tol; 156 | end 157 | trace_label = 'CMT:szego:invtheta'; 158 | 159 | % Should check that 1) s is monotonically increasing, and 2) s(end) < 160 | % 2*pi. 161 | if any(diff(s) < 0) 162 | error('CMT:InvalidArgument', 'Input s must be strictly monotonic.') 163 | end 164 | if any(s == 2*pi) 165 | warning('CMT:BadIdea', ... 166 | ['Passing 2*pi as a value of s may have unpredictible' ... 167 | ' results. Use 0 instead.']) 168 | end 169 | 170 | f = @(t, s) s - mod(theta(S, t), 2*pi); 171 | % Start with a proportionally spaced guess. Hey, you gotta start 172 | % somewhere, and sometimes you get lucky. 173 | t = s/(2*pi); 174 | 175 | % Bisection generates initial guess for Newton. We take advantage of 176 | % the fact that, as a function of t, 177 | % f(t, s) := s - theta(S, t) 178 | % on the proper branch is monotonically decreasing. 179 | btol = 1e-3; % Solves problem to order 1e-4 for Newton. 180 | bmaxiter = 20; % No runaways! 181 | 182 | % Using convergence rate of bisection method to get an initial partition of 183 | % [0, length(curve)) so bisection finishes closeish to 5 steps. There ends up 184 | % being an evaluation at a large number of points, but it's only once. 185 | nb = max(ceil(1/(2^4*btol)), numel(t)); 186 | if nb > numel(t) 187 | tt = (0:nb-1)'/nb; 188 | else 189 | tt = t; 190 | end 191 | th = mod(theta(S, tt), 2*pi); 192 | 193 | % Sign change indicates where zero lives. 194 | [chg,colk] = find(diff(sign(bsxfun(@minus, s', th))) == -2); 195 | left = zeros(size(t)); 196 | left(colk) = tt(chg); 197 | right = ones(size(t)); 198 | right(colk) = tt(chg+1); 199 | 200 | % Bisect. 201 | done = abs(f(t, s)) < btol; 202 | biter = 0; 203 | if S.beNoisy 204 | fprintf('%s: Starting bisection ...\n', trace_label) 205 | end 206 | while ~all(done) && biter < bmaxiter 207 | biter = biter + 1; 208 | 209 | t(~done) = 0.5*(left(~done) + right(~done)); 210 | fk = f(t(~done), s(~done)); 211 | isneg = fk < 0; 212 | left(~done) = isneg.*left(~done) + ~isneg.*t(~done); 213 | right(~done) = isneg.*t(~done) + ~isneg.*right(~done); 214 | done(~done) = abs(fk) < btol; 215 | end 216 | if S.beNoisy 217 | fprintf('%s: Bisection finished in %d steps.\n', ... 218 | trace_label, biter) 219 | end 220 | 221 | % Apply Newton's method. 222 | nmaxiter = 20; % Should probably be set somewhere else. 223 | 224 | fval = f(t, s); 225 | done = abs(fval) < ntol; 226 | update = double(~done); 227 | prev_update = nan(size(update)); 228 | 229 | niter = 0; 230 | if S.beNoisy 231 | fprintf('%s: Starting Newton iteration ...\n', trace_label) 232 | end 233 | while ~all(done) && niter < nmaxiter 234 | niter = niter + 1; 235 | 236 | update(~done) = fval(~done)./thetap(S, t(~done)); 237 | t(~done) = t(~done) + update(~done); 238 | 239 | if all(abs(abs(prev_update(~done)) - abs(update(~done))) <= 100*eps) 240 | break 241 | end 242 | prev_update = update; 243 | 244 | fval(~done) = f(t(~done), s(~done)); 245 | done(~done) = abs(fval(~done)) < ntol; 246 | update(done) = 0; 247 | end 248 | if S.beNoisy 249 | fprintf('%s: Newton finished in %d steps.\n', ... 250 | trace_label, niter) 251 | end 252 | 253 | maxerr = max(abs(fval)); 254 | if S.beNoisy 255 | fprintf('%s: %d/%d points with |f| > eps, max|f| = %.4e.\n\n', ... 256 | trace_label, sum(~done), numel(t), max(abs(fval))) 257 | end 258 | if maxerr > 100*ntol 259 | warning('CMT:OutOfTolerance', ... 260 | ['Newton iteration finished with maxerr=%.6e (>100*tol).\n' ... 261 | 'Check output (maybe szego needs a larger N?).'], ... 262 | maxerr) 263 | end 264 | 265 | if nargout > 1 266 | output.biter = biter; 267 | output.niter = niter; 268 | output.maxerr = max(abs(fval)); 269 | output.done = sum(done); 270 | end 271 | end 272 | 273 | function thp = thetap(S, t) 274 | % Derivative of theta. 275 | thp = 2*pi/S.Saa*abs(phi(S, t(:)).^2); 276 | end 277 | end 278 | 279 | methods(Access=protected, Static) 280 | function out = compute_kernel(curve, a, opts) 281 | noisy = opts.trace; 282 | N = opts.numCollPts; 283 | 284 | % Parameter length should be 1, if not this should be length(curve)/N. 285 | dt = 1/N; 286 | t = (0:N-1)'*dt; 287 | z = point(curve, t); 288 | zt = tangent(curve, t); 289 | zT = zt./abs(zt); 290 | 291 | if noisy 292 | fprintf('\nSzego constructor:\n\n') 293 | 294 | fprintf(' Creating Kerzmann-Stein kernel for %d collocation points...', ... 295 | N); 296 | end 297 | 298 | % IpA = I + A, where A is the Kerzmann-Stein kernel. 299 | IpA = ones(N); 300 | for j = 2:N 301 | cols = 1:j-1; 302 | zc_zj = z(cols) - z(j); 303 | IpA(j,cols) = (conj(zT(j)./zc_zj) ... 304 | - zT(cols)./zc_zj) ... 305 | .* sqrt(abs(zt(j)*zt(cols))) * (dt/(2i*pi)); 306 | IpA(cols,j) = -IpA(j,cols)'; 307 | end 308 | 309 | if noisy 310 | fprintf('\n') 311 | end 312 | 313 | y = 1i*sqrt(abs(zt))/(2*pi) .* conj(zT./(z - a)); 314 | 315 | if noisy 316 | fprintf(' Solving for Szego kernel at collocation points using ') 317 | end 318 | 319 | if strcmp(opts.kernSolMethod, 'auto') 320 | if N < 2048 321 | method = 'bs'; 322 | else 323 | method = 'or'; 324 | end 325 | else 326 | method = opts.kernSolMethod; 327 | end 328 | if any(strcmp(method, {'backslash', 'bs'})) 329 | if noisy 330 | fprintf('backslash...') 331 | end 332 | x = IpA\y; 333 | elseif any(strcmp(method, {'orth_resid', 'or'})) 334 | if noisy 335 | fprintf('orthogonal residuals...\n') 336 | end 337 | % Need initial guess; get it via interpolation. 338 | sargs = varargs(opts); 339 | tmp = szego(curve, sargs{:}, 'numCollPts', 256); 340 | x = phi(tmp, t); 341 | 342 | % Pass guess to solver. 343 | x = szego.orthog_resid(IpA, x, y, noisy); 344 | else 345 | error('CMT:InvalidArgument', 'Invalid solution method requested.') 346 | end 347 | if noisy 348 | fprintf('\n') 349 | end 350 | 351 | relresid = norm(y - IpA*x)/norm(y); 352 | if relresid > 100*eps 353 | warning('CMT:OutOfTolerance', ... 354 | 'Relative residual %.4g is larger than 100*eps.', ... 355 | relresid) 356 | end 357 | 358 | if noisy 359 | fprintf(' Kernel solution relative residual is %.4e.\n\n', relresid) 360 | end 361 | 362 | out.phiColl = x; 363 | out.dtColl = dt; 364 | out.zPts = z; 365 | out.zTan = zt; 366 | out.zUnitTan = zT; 367 | out.relResid = relresid; 368 | end 369 | 370 | function x = orthog_resid(A, x, y, noisy) 371 | % Method of orthogonal residuals. See p. 857 in Trummer. 372 | if nargin < 4 || isempty(noisy) 373 | noisy = false; 374 | end 375 | 376 | if noisy 377 | fprintf('\n iter orth-resid\n') 378 | fprintf(' ---- ----------\n') 379 | end 380 | 381 | maxiter = 20; 382 | omeg = 1; 383 | xp = x; 384 | xpp = zeros(size(x)); 385 | yip = y'*y; 386 | for iter = 1:maxiter 387 | r = y - A*x; 388 | 389 | rip = r'*r; 390 | if noisy 391 | fprintf(' %2d %.4e\n', iter, sqrt(rip/yip)); 392 | end 393 | if sqrt(rip/yip) <= eps 394 | break 395 | end 396 | if iter > 1 397 | omeg = 1/(1 + rip/ripp/omeg); 398 | end 399 | ripp = rip; 400 | 401 | x = xpp + omeg*(r + xp - xpp); 402 | xpp = xp; 403 | xp = x; 404 | end 405 | end 406 | end 407 | 408 | end 409 | -------------------------------------------------------------------------------- /szmap.m: -------------------------------------------------------------------------------- 1 | classdef szmap < conformalmap 2 | % SZMAP represents a Riemann map via the Szego kernel. 3 | % 4 | % f = szmap(range, a) 5 | % f = szmap(range, a, opts) 6 | % Creates a Riemann map from the interior of the unit disk to the interior of 7 | % the region via the Szego kernel and the FFT with normalization f(0) = a and 8 | % f'(0) > 0. Various options may be set via the opts parameter which must be a 9 | % szset object. 10 | % 11 | % See also szego, szset. 12 | 13 | % This file is a part of the CMToolbox. 14 | % It is licensed under the BSD 3-clause license. 15 | % (See LICENSE.) 16 | 17 | % Copyright Toby Driscoll, 2014. 18 | % Written by Everett Kropf, 2014. 19 | 20 | properties 21 | theKernel 22 | coefficients 23 | sopts 24 | end 25 | 26 | methods 27 | function f = szmap(range, varargin) 28 | if nargin 29 | if isa(range, 'closedcurve') 30 | range = region(range); 31 | end 32 | if ~issimplyconnected(range) 33 | error('CMT:InvalidArgument', ... 34 | 'Region must be simply connected.') 35 | end 36 | args = {unitdisk, range}; 37 | else 38 | args = {}; 39 | end 40 | 41 | f = f@conformalmap(args{:}); 42 | opts = get(f, szset); 43 | 44 | if ~nargin 45 | return 46 | end 47 | 48 | if nargin > 2 49 | opts = set(opts, varargin{:}); 50 | end 51 | 52 | boundary = outer(range); 53 | 54 | szargs = varargs(opts); 55 | S = szego(boundary, szargs{:}); 56 | 57 | nF = opts.numFourierPts; 58 | t = invtheta(S, 2*pi*(0:nF-1)'/nF); 59 | c = flipud(fft(boundary(t))/nF); 60 | 61 | f.theKernel = S; 62 | f.coefficients = c; 63 | f.sopts = opts; 64 | end 65 | 66 | function g = ctranspose(f) 67 | d = domain(f); 68 | if isinterior(d) 69 | d = diskex(outer(d)); 70 | else 71 | d = disk(inner(d)); 72 | end 73 | 74 | r = range(f); 75 | if isinterior(r) 76 | br = outer(r); 77 | r = region(br, 'exteriorto'); 78 | else 79 | br = inner(r); 80 | r = region(br); 81 | end 82 | 83 | prefs = varargs(f.sopts); 84 | a = f.sopts.confCenter; 85 | g = szmap(cinvcurve(br, a), prefs{:}, 'confCenter', 0); 86 | func = @(z) 1./conj(applyMapPrivate(g, conj(1./z))) + a; 87 | 88 | g.theDomain = d; 89 | g.theRange = r; 90 | g.functionList = {func}; 91 | end 92 | 93 | function S = kernel(f) 94 | S = f.theKernel; 95 | end 96 | end 97 | 98 | methods(Access=protected) 99 | function w = applyMap(f, z) 100 | if isanonymous(f) 101 | w = applyMap@conformalmap(f, z); 102 | else 103 | w = applyMapPrivate(f, z); 104 | end 105 | end 106 | end 107 | 108 | methods(Access=private) 109 | function w = applyMapPrivate(f, z) 110 | w = polyval(f.coefficients, z); 111 | end 112 | end 113 | 114 | end 115 | -------------------------------------------------------------------------------- /szset.m: -------------------------------------------------------------------------------- 1 | classdef szset < optset 2 | % SZSET is the options structure for the szego kernel. 3 | % 4 | % opts = szset('name', value, ...) 5 | % Creates option structure for szego via name/value pairs. 6 | % 7 | % Properties: 8 | % numCollPts -- Number of collcation points. 9 | % kernSolMethod -- Solver method. 10 | % newtonTol -- Newton iteration tolerance. 11 | % trace -- Print out solution trace information. 12 | % numFourierPts -- Default size of FFT to employ. 13 | % 14 | % Methods: 15 | % defaults(szset) 16 | % Shows properties which may be set along with defaults. 17 | % 18 | % See also szego. 19 | 20 | % This file is a part of the CMToolkit. 21 | % It is licensed under the BSD 3-clause license. 22 | % (See LICENSE.) 23 | 24 | % Copyright Toby Driscoll, 2014. 25 | % Written by Everett Kropf, 2014. 26 | 27 | properties 28 | confCenter % Conformal center. 29 | numCollPts % Number of collcation points. 30 | kernSolMethod % Solver method. 31 | newtonTol % Newton iteration tolerance. 32 | trace % Print out solution trace information. 33 | numFourierPts % Default size of FFT to employ. 34 | end 35 | 36 | properties(Access=protected) 37 | proplist = ... 38 | { 39 | 'confCenter', 0, ... 40 | @szset.isScalarDouble, '[ scalar double {0} ]' 41 | 'numCollPts', 512, ... 42 | @szset.isPosInteger, '[ integer {512} ]' 43 | 'kernSolMethod', 'auto', ... 44 | @szset.isOkMethod, '[ backslash | orth_resid | {auto} ]' 45 | 'newtonTol', 10*eps(2*pi), ... 46 | @szset.isScalarDouble, '[ scalar double {10*eps(2*pi)} ]' 47 | 'trace', false, [], '[ true | {false} ]' 48 | 'numFourierPts', 256, ... 49 | @szset.isPosInteger, '[ integer {256} ]' 50 | } 51 | end 52 | 53 | methods 54 | function opt = szset(varargin) 55 | opt = opt@optset(varargin{:}); 56 | end 57 | end 58 | 59 | methods(Static, Hidden) 60 | function tf = isPosInteger(x) 61 | tf = (x - fix(x)) == 0 & x > 0; 62 | end 63 | 64 | function tf = isOkMethod(s) 65 | tf = any(strcmp(s, {'backslash', 'orth_resid', 'auto'})); 66 | end 67 | 68 | function tf = isScalarDouble(x) 69 | tf = numel(x) == 1 & isa(x, 'double'); 70 | end 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /test/masterTest.m: -------------------------------------------------------------------------------- 1 | classdef masterTest < matlab.unittest.TestCase 2 | % Common setup/teardown for all tests. 3 | % * Set path for running tests. 4 | 5 | % This file is a part of the CMToolbox. 6 | % It is licensed under the BSD 3-clause license. 7 | % (See LICENSE.) 8 | 9 | % Copyright Toby Driscoll, 2014. 10 | % Written by Everett Kropf, 2014. 11 | 12 | methods(TestClassSetup) 13 | function addThePath(test) 14 | test.addTeardown(@path, addpath('..')); 15 | end 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/testCircle.m: -------------------------------------------------------------------------------- 1 | classdef testCircle < masterTest 2 | % Test class for circle. 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | methods(Test) 12 | function threePointCheck(test) 13 | gc = circle([0, 5, 7i]); 14 | test.verifyEqual(radius(gc), hex2num('40113463fa37014d'), ... 15 | 'AbsTol', 1e-15); 16 | end 17 | 18 | function zlineCheck(test) 19 | z1 = circle([0, 1, inf]); 20 | cond = isinside(z1, 1i) && ~isinside(z1, -1i); 21 | test.verifyTrue(cond); 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /test/testHomog.m: -------------------------------------------------------------------------------- 1 | classdef testHomog < masterTest 2 | % Unit tests for HOMOG class. 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | methods(Test) 12 | function createHomogNumbers(test) 13 | h = [0; homog(1,0); homog(1i,0); 1i]; 14 | test.verifyEqual(double(h), [0; Inf; Inf; 1i]); 15 | end 16 | 17 | function checkHomogAngle(test) 18 | h = [0; homog(1,0); homog(1i,0); 1i]; 19 | test.verifyEqual(angle(h), pi/2*[0; 0; 1; 1]); 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/testMobius.m: -------------------------------------------------------------------------------- 1 | classdef testMobius < masterTest 2 | % Test class for mobius. 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | properties 12 | maps 13 | data 14 | end 15 | 16 | methods(TestMethodSetup) 17 | function createMaps(test) 18 | z3 = [1, 1i, -1]; 19 | w3 = [-1, -2i, 0]; 20 | m1 = mobius(z3, w3); 21 | s3 = [0, 3, 1i]; 22 | m2 = mobius(w3, s3); 23 | test.maps = struct('m1', m1, 'm2', m2); 24 | test.data = struct('z3', z3, 'w3', w3, 's3', s3); 25 | end 26 | end 27 | 28 | methods(Test) 29 | function basicMobiusCheck(test) 30 | M = mobius([1, 1i, -1], [0, 5, 7i]); 31 | R = inv(M); 32 | cond = isequal(size(M.matrix), [2, 2]) ... 33 | && isequal(size(R.matrix), [2, 2]); 34 | test.verifyTrue(cond); 35 | end 36 | 37 | function compositionCheck1(test) 38 | z3 = test.data.z3; 39 | m1 = test.maps.m1; 40 | m2 = test.maps.m2; 41 | m = m2*m1; 42 | test.verifyLessThan(norm(m2(m1(z3)).' - m(z3).'), 1e-15) 43 | end 44 | 45 | function compositionCheck2(test) 46 | z3 = test.data.z3; 47 | m1 = test.maps.m1; 48 | m2 = test.maps.m2; 49 | m = conformalmap(m1, m2); 50 | test.verifyLessThan(norm(m2(m1(z3)).' - m(z3).'), 1e-15) 51 | end 52 | 53 | function plotCheck(test) 54 | m1 = test.maps.m1; 55 | m2 = test.maps.m2; 56 | h = figure; 57 | plot(m1) 58 | clf 59 | plot(m2*m1) 60 | close(h) 61 | end 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /test/testPolygon.m: -------------------------------------------------------------------------------- 1 | classdef testPolygon < masterTest 2 | % Test class for polygon class 3 | 4 | % This file is a part of the CMToolbox. 5 | % It is licensed under the BSD 3-clause license. 6 | % (See LICENSE.) 7 | 8 | % Copyright Toby Driscoll, 2014. 9 | % Written by Everett Kropf, 2014. 10 | 11 | methods(Test) 12 | function basicPolygonCheck(test) 13 | p1 = polygon([0, 1, 1+1i, 1i]); 14 | % Took out because polygon temporarily changed to match SCT. 15 | % p2 = polygon([0, homog(1, 0), homog(1i, 0)]); 16 | cond = sum(angle(p1)) == 2; % && sum(angle(p2)) == 0; 17 | test.verifyTrue(cond); 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/testRegion.m: -------------------------------------------------------------------------------- 1 | classdef testRegion < masterTest 2 | 3 | % This file is a part of the CMToolbox. 4 | % It is licensed under the BSD 3-clause license. 5 | % (See LICENSE.) 6 | 7 | % Copyright Toby Driscoll, 2014. 8 | % Written by Everett Kropf, 2014. 9 | 10 | methods(Test) 11 | function basicRegionCheck(test) 12 | c0 = circle(0, 1); 13 | c1 = circle(0.41+0.16i, 0.24); 14 | c2 = circle(-0.53+0.09i, 0.12); 15 | c3 = circle(-0.10-0.34i, 0.25); 16 | 17 | r = region(c0, {c1, c2, c3}); 18 | 19 | cond = hasouter(r) & r.numouter == 1 & hasinner(r) & r.numinner == 3; 20 | test.verifyTrue(cond); 21 | end 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /unitdisk.m: -------------------------------------------------------------------------------- 1 | function d = unitdisk() 2 | % UNITDISK creates a unit disk region. 3 | % 4 | % d = unitdisk() 5 | % Creates the unit disk region by d = disk(0, 1). 6 | 7 | % This file is a part of the CMToolbox. 8 | % It is licensed under the BSD 3-clause license. 9 | % (See LICENSE.) 10 | 11 | % Copyright Toby Driscoll, 2014. 12 | % Written by Everett Kropf, 2014. 13 | 14 | d = disk(0, 1); 15 | --------------------------------------------------------------------------------