0001 function imdl = mk_geophysics_model(str, ne, opt);
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073
0074
0075
0076
0077
0078
0079
0080
0081
0082
0083
0084
0085 if ischar(str) && strcmp(str,'UNIT_TEST'); do_unit_test; return; end
0086 copt.fstr = 'mk_geophysics_model';
0087 if nargin < 3
0088 opt = {};
0089 end
0090 SALT='z$Id: mk_geophysics_model.m 5903 2019-02-05 11:08:03Z aadler $';
0091 imdl = eidors_cache(@mk_model,{str, ne, opt, SALT}, copt);
0092
0093 function imdl=mk_model(str,ne,opt,SALT);
0094 if str(1) ~= 'h' && str(1) ~= 'H'
0095 error([str ': I only know how to build linear half-space models: h***']);
0096 end
0097
0098 MDL_2p5D_CONFIG = 0;
0099 switch str(2:end-1)
0100 case {'2', '3'}
0101 FMDL_DIM = str(2) - '0';
0102 CMDL_DIM = 0;
0103 case '2p5'
0104 FMDL_DIM = 2;
0105 CMDL_DIM = 0;
0106 MDL_2p5D_CONFIG = 1;
0107 case {'22', '33', '32'}
0108 FMDL_DIM = str(2) - '0';
0109 CMDL_DIM = str(3) - '0';
0110 otherwise
0111 error([str ': unrecognized model type']);
0112 end
0113 assert(CMDL_DIM ~= 3, '3d rec_model not yet tested');
0114
0115 skip_c2f = 0;
0116 if str(1) == 'h'
0117 elec_width = 0.1;
0118 else
0119 elec_width = 0;
0120 end
0121 z_contact= 0.01;
0122 nodes_per_elec= 3;
0123 elec_spacing= 5.0;
0124 threshold = 1e-12;
0125 save_model_to_disk = (length(ne) == 1) && (length(opt) == 0);
0126
0127 extend_x = 1;
0128 extend_y = 1;
0129 extend_z = 1;
0130 extend_inner_x = 3/5;
0131 extend_inner_y = 3/5;
0132 extend_inner_z = 2/5;
0133 build_stim = 'Wenner';
0134 extra_ng_code = '';
0135 if length(opt) > 0
0136 assert(round(length(opt)/2)*2 == length(opt),'option missing value?');
0137 expect = {'hmax_rec','hmax_fwd', 'hmax_fwd_inner', ...
0138 'elec_width','z_contact','elec_spacing',...
0139 'extend_x', 'extend_y', 'extend_z', ...
0140 'extend_inner_x', 'extend_inner_y', 'extend_inner_z', ...
0141 'skip_c2f', 'threshold', 'build_stim', 'extra_ng_code'};
0142 opts = struct(opt{:})
0143 for i = fieldnames(opts)'
0144 assert(any(strcmp(i,expect)), ['unexpected option: ',i{:}]);
0145 eval([i{:} ' = opts.(i{:});']);
0146 end
0147 if (str(1) == 'H') && isfield(opts, 'elec_width')
0148 error('requested "H" PEM model but configured "elec_width" option');
0149 end
0150 end
0151 if length(ne) == 1
0152 xw=(ne-1)*elec_spacing;
0153
0154 xs=+5;
0155 xyz = xs+([1:ne]'-1)*elec_spacing;
0156 else
0157 xyz = ne;
0158 end
0159 if size(xyz,1) == 1
0160 xyz = xyz';
0161 end
0162 xyz = [xyz zeros(size(xyz,1),3-size(xyz,2))];
0163 ne=size(xyz,1);
0164 [R, X] = rot_line_to_xaxis(xyz);
0165
0166 xyzc = (xyz - X)*R;
0167
0168 xw=max(xyzc(:,1))-min(xyzc(:,1));
0169 xs=min(xyzc(:,1));
0170 elec_spacing = min(min(pdist(xyzc) + diag(inf*(1:size(xyzc,1)))));
0171
0172 if ~exist('hmax_fwd','var')
0173 if str(end)-'a' >= 0
0174 hmax_fwd = xw*2^-(str(end)-'a');
0175 else
0176 hmax_fwd = elec_spacing*2^-(str(end)-'A'-4);
0177 end
0178 end
0179 if ~exist('hmax_rec','var')
0180 hmax_rec=hmax_fwd*2.01;
0181 end
0182 if ~exist('hmax_fwd_inner','var')
0183 hmax_fwd_inner=hmax_fwd/2.0;
0184 end
0185
0186 if save_model_to_disk
0187 isOctave = exist('OCTAVE_VERSION');
0188 if isOctave
0189 octavestr='-octave-';
0190 else
0191 octavestr='-';
0192 end
0193
0194 filename=fullfile(eidors_cache('cache_path'),sprintf('mk_geophysics_model%simdl-%s-%03del.mat',octavestr,str,ne));
0195 if exist(filename, 'file') == 2
0196 tmp = whos('-file',filename,'SALT');
0197 if length(tmp) > 0
0198 tmp = load(filename,'SALT');
0199 fSALT = tmp.SALT;
0200 else
0201 fSALT = 'deadbeef';
0202 end
0203 if strcmp(fSALT, SALT)
0204 tmp=load(filename,'imdl');
0205 imdl = tmp.imdl;
0206 eidors_msg(sprintf('%s: %s, %d electrode model loaded from file',filename,str,ne));
0207 return
0208 end
0209 end
0210 end
0211
0212 assert(extend_x>0,'extend_x must be > 0');
0213 assert(extend_y>0,'extend_y must be > 0');
0214 assert(extend_z>-1,'extend_z must be > -1');
0215
0216
0217
0218
0219 fmdlbox = [-(1+2*extend_x) +(1+2*extend_x);
0220 -(1+2*extend_y) +(1+2*extend_y);
0221 -(2+2*extend_z) +3];
0222 fmdlinbox = [-(1+2*extend_inner_x) +(1+2*extend_inner_x);
0223 -(1+2*extend_inner_y) +(1+2*extend_inner_y);
0224 -(2+2*extend_inner_z) +2];
0225 cmdlbox = fmdlbox;
0226
0227 assert(all(fmdlbox(:,1) <= fmdlinbox(:,1)), 'oops, inner mesh must be smaller than outer mesh');
0228 assert(all(fmdlbox(:,2) >= fmdlinbox(:,2)), 'oops, inner mesh must be smaller than outer mesh');
0229 assert(all(fmdlbox(:,1) <= cmdlbox(:,1)), 'oops, reconstruction mesh must be smaller than forward mesh');
0230 assert(all(fmdlbox(:,2) >= cmdlbox(:,2)), 'oops, reconstruction mesh must be smaller than forward mesh');
0231
0232
0233 if elec_width ~= 0
0234 elec_shape = [elec_width*norm(R)/2, 0, elec_width*norm(R)/2/(nodes_per_elec-1)];
0235 elec_pos = [ xyzc(:,1:FMDL_DIM), repmat([zeros(1,3-FMDL_DIM+2) 1],ne,1) ];
0236 cem2pem = 0;
0237 else
0238 if FMDL_DIM == 3
0239 elec_width = elec_spacing/2;
0240 elec_shape = [elec_width, elec_width, hmax_fwd_inner];
0241 elec_pos = [ xyzc, repmat([0 0 1],ne,1) ];
0242 elec_pos(:,1) = elec_pos(:,1) + elec_width/2;
0243 elec_pos(:,2) = elec_pos(:,2) + elec_width/2;
0244 cem2pem = 1;
0245 else
0246 elec_width = elec_spacing/2;
0247 elec_shape = [elec_width, elec_width, hmax_fwd_inner];
0248 elec_pos = [ xyzc(:,1:2), repmat([0 0 0 1],ne,1) ];
0249 elec_pos(:,1) = elec_pos(:,1) + elec_width/2;
0250 cem2pem = 1;
0251 end
0252 end
0253 skip_tlo = '';
0254 for idx = strfind(extra_ng_code, 'tlo ')
0255 sc = strfind(extra_ng_code, ';');
0256 sc = min(sc + (sc<idx)*1e10);
0257 tlo = extra_ng_code(idx+4:sc-1);
0258 skip_tlo = [skip_tlo ' and (not ' tlo ')'];
0259 end
0260 elec_pos(:,FMDL_DIM) = 0;
0261 if FMDL_DIM == 3
0262 shape_str = [...
0263 sprintf('solid ps = plane(%s);\n', a2s([0 0 0; 0 0 1])), ...
0264 sprintf('solid bi = orthobrick(%s);\n', a2s(fmdlinbox')), ...
0265 sprintf('solid bo = orthobrick(%s);\n', a2s(fmdlbox')), ...
0266 extra_ng_code, ...
0267 sprintf('solid ri = bi and ps%s -maxh=%f;\n', skip_tlo, hmax_fwd_inner), ...
0268 sprintf('solid ro = bo and ps and (not bi) -maxh=%f;\n', hmax_fwd), ...
0269 sprintf('solid mainobj = ri;\n')];
0270
0271
0272 else
0273
0274
0275
0276 sw = range(fmdlinbox(1,:)) / 5;
0277 sw = min(sw,2*hmax_fwd);
0278 ri2d = fmdlinbox'; ri2d(:,2) = [-sw 0 ];
0279 ro2d = fmdlbox'; ro2d(:,2) = [-sw 0 ];
0280 shape_str = [...
0281 sprintf('solid ps = plane(%s);\n', a2s([0 0 0; 0 0 1])), ...
0282 sprintf('solid bi = orthobrick(%s);\n', a2s(ri2d)), ...
0283 sprintf('solid bo = orthobrick(%s);\n', a2s(ro2d)), ...
0284 extra_ng_code, ...
0285 sprintf('solid ri = bi and ps%s -maxh=%f;\n', skip_tlo, hmax_fwd_inner), ...
0286 sprintf('solid ro = bo and ps and (not bi) -maxh=%f;\n', hmax_fwd), ...
0287 sprintf('solid mainobj = ri;\n')];
0288 end
0289
0290 elec_obj = 'ps';
0291 [fmdl, mat_idx] = ng_mk_gen_models(shape_str, elec_pos, elec_shape, elec_obj, 'tlo ro');
0292 if FMDL_DIM == 2
0293
0294 [fmdl, mat_idx] = copy_mdl2d_from3d(fmdl, mat_idx, 'y');
0295 else
0296 if CMDL_DIM ~= 0
0297
0298 cmdl.mk_coarse_fine_mapping.f2c_offset = [0 0 0];
0299 cmdl.mk_coarse_fine_mapping.f2c_project = [1 0 0; 0 0 1; 0 1 0];
0300
0301 cmdl.mk_analytic_c2f.f2c_offset = cmdl.mk_coarse_fine_mapping.f2c_offset;
0302 cmdl.mk_analytic_c2f.f2c_project = cmdl.mk_coarse_fine_mapping.f2c_project;
0303 end
0304 end
0305
0306 if cem2pem
0307 fmdl = convert_cem2pem(fmdl, xyzc);
0308 end
0309
0310
0311 xllim = fmdlbox(1,1);
0312 xrlim = fmdlbox(1,2);
0313 zdepth = fmdlbox(3,1);
0314 xr=max(floor((xrlim-xllim)/hmax_rec/2),1)*2+1;
0315 yr=max(floor(-zdepth/hmax_rec/2),1)*2+1;
0316 [x,y] = meshgrid( linspace(xllim,xrlim,xr), linspace(zdepth,0,yr));
0317 vtx= [x(:),y(:)];
0318 if CMDL_DIM ~= 0
0319 cmdl= mk_fmdl_from_nodes( vtx,{vtx(1,:)}, z_contact, 'sq_m2');
0320 end
0321
0322
0323 for i=1:ne
0324 n=fmdl.electrode(i).nodes;
0325 nn=length(n);
0326 nx=fmdl.nodes(n,:);
0327
0328 fmdl.electrode(i).z_contact = z_contact;
0329 if CMDL_DIM ~= 0
0330 nnc = length(cmdl.nodes);
0331 cmdl.nodes = [cmdl.nodes; nx(:,[1 FMDL_DIM])];
0332 cmdl.electrode(i).nodes = (nnc+1):(nnc+nn);
0333 cmdl.electrode(i).z_contact = z_contact;
0334 end
0335 end
0336
0337
0338 elec_err = sqrt(sum(mdl_elec_err(fmdl, xyzc).^2,2));
0339 if max(elec_err) > threshold
0340 [fmdl, cf] = correct_electrode_positions(fmdl, xyzc);
0341
0342 if CMDL_DIM ~= 0
0343 [cmdl, cc] = correct_electrode_positions(cmdl, xyzc);
0344 end
0345 end
0346
0347 fmdl.mv_elec = @shift_electrode_positions;
0348
0349
0350 nn = size(fmdl.nodes,1);
0351 Xn = repmat(X(1,:), nn, 1);
0352 fmdl.nodes = ([fmdl.nodes zeros(nn,3-FMDL_DIM)]/ R) + Xn;
0353 fmdl.nodes = fmdl.nodes(:,1:FMDL_DIM);
0354
0355 if CMDL_DIM ~= 0
0356 nn = size(cmdl.nodes,1);
0357 Xn = repmat(X(1,:), nn, 1);
0358 if CMDL_DIM ~= FMDL_DIM
0359 cmdl.nodes = ([cmdl.nodes(:,1) zeros(nn,1) cmdl.nodes(:,2:end)]/ R) + Xn;
0360 cmdl.nodes = cmdl.nodes(:,[1 3])/R([1 3],[1 3]);
0361 else
0362 cmdl.nodes = ([cmdl.nodes zeros(nn,3-CMDL_DIM)]/ R) + Xn;
0363 cmdl.nodes = cmdl.nodes(:,1:CMDL_DIM);
0364 end
0365 end
0366
0367
0368 for i = 1:length(fmdl.electrode)
0369 nn = fmdl.electrode(i).nodes;
0370 assert(length(nn(:)) > 0, sprintf('electrode#%d: failed to find nodes',i));
0371 end
0372
0373
0374
0375
0376 [~, gn] = min(fmdl.nodes(:,end));
0377 fmdl.gnd_node = gn;
0378 if CMDL_DIM ~= 0
0379 [~, gn] = min(cmdl.nodes(:,end));
0380 cmdl.gnd_node = gn;
0381 end
0382
0383
0384 imdl= mk_common_model('a2d0c',4);
0385 imdl.fwd_model = fmdl;
0386 imdl.name = ['EIDORS mk_geophysics_model ' str];
0387 imdl.fwd_model.name = ['EIDORS mk_geophysics_model fwd model ' str];
0388 if CMDL_DIM ~= 0
0389 imdl.rec_model.name = ['EIDORS mk_geophysics_model rec model ' str];
0390 imdl.rec_model = cmdl;
0391
0392
0393
0394
0395 if ~skip_c2f
0396 [c2f,out] = mk_approx_c2f(fmdl,cmdl);
0397 imdl.fwd_model.coarse2fine = c2f;
0398 imdl.fwd_model.background = out;
0399 end
0400 end
0401
0402 if MDL_2p5D_CONFIG
0403 imdl.fwd_model.jacobian = @jacobian_adjoint_2p5d_1st_order;
0404 imdl.fwd_model.solve = @fwd_solve_2p5d_1st_order;
0405 imdl.fwd_model.system_mat = @system_mat_2p5d_1st_order;
0406 imdl.fwd_model.jacobian_adjoint_2p5d_1st_order.k = [0 3];
0407 imdl.fwd_model.fwd_solve_2p5d_1st_order.k = [0 3];
0408 end
0409
0410 if ~strcmp(build_stim,'none')
0411 imdl.fwd_model.stimulation = stim_pattern_geophys(length(imdl.fwd_model.electrode), build_stim);
0412 end
0413
0414 if save_model_to_disk
0415 save(filename,'imdl','SALT');
0416 end
0417
0418
0419
0420
0421 function s = a2s(a)
0422 if length(a(:)) == 3
0423 s = sprintf('%f,%f,%f', ...
0424 a(1), a(2), a(3));
0425 else
0426 s = sprintf('%f,%f,%f;%f,%f,%f', ...
0427 a(1,1), a(1,2), a(1,3), ...
0428 a(2,1), a(2,2), a(2,3));
0429 end
0430
0431
0432
0433 function [R,X,var] = rot_line_to_xaxis(xyz)
0434 x = xyz(:,1); y=xyz(:,2); z=xyz(:,3);
0435
0436
0437
0438 p = mean(xyz);
0439 [U,S,V] = svd([x-p(1), y-p(2), z-p(3)]);
0440 if V(end,1) ~= 0
0441 N=1/V(end,1)*V(:,1);
0442 else
0443 N=V(:,1);
0444 end
0445 A=p' + dot( xyz(1, :) - p, N ) * N/norm(N)^2;
0446 B=p' + dot( xyz(end,:) - p, N ) * N/norm(N)^2;
0447
0448
0449 a = N/norm(N);
0450 b = [ 1 0 0 ]';
0451
0452 v = cross(a, b);
0453 s = norm(v); c = dot(a, b);
0454 xt = @(v) [ 0 -v(3) v(2); ...
0455 v(3) 0 -v(1); ...
0456 -v(2) v(1) 0];
0457 if abs(s) < eps*1e3
0458 R = eye(3);
0459 else
0460 R = (eye(3) + xt(v) + xt(v)^2 * (1-c)/s^2);
0461 R=R';
0462 end
0463
0464 X = repmat(p, size(xyz,1), 1);
0465 R = R / max(range((xyz-X)*R)) * 2;
0466
0467 DEBUG=0;
0468 if DEBUG
0469 clf;
0470 subplot(211);
0471 plot3(x,y,z,'bo');
0472 hold on;
0473 plot3(x(1),y(1),z(1),'go');
0474 plot3(p(1),p(2),p(3),'ro');
0475 plot3([A(1),B(1)],[A(2),B(2)],[A(3),B(3)]);
0476 grid; axis equal; hold off;
0477 xlabel('x'); ylabel('y'); zlabel('z');
0478 title('pre-rotation');
0479 subplot(212);
0480 xx = (xyz-X)*R;
0481 plot3(xx(:,1), xx(:,2), xx(:,3),'bo');
0482 hold on;
0483 plot3(xx(1,1), xx(1,2), xx(1,3),'go');
0484 plot3(0,0,0,'ro');
0485 grid; axis equal; hold off;
0486 xlabel('x'); ylabel('y'); zlabel('z');
0487 title('post-rotation');
0488 end
0489
0490
0491 function r = range(a)
0492 r = max(a(:))-min(a(:));
0493
0494 function [mdl2,idx2] = copy_mdl2d_from3d(mdl3,idx3,sel);
0495
0496
0497 if sel == 'x'
0498
0499 T = [ 0 0 1; 0 1 0; 1 0 0 ];
0500 elseif sel == 'y'
0501
0502 T = [ 1 0 0; 0 0 1; 0 1 0 ];
0503 elseif sel == 'z'
0504 T = eye(3);
0505 else
0506 error('sel must be "x", "y" or "z"');
0507 end
0508 mdl3.nodes = mdl3.nodes * T;
0509
0510
0511 mdl2 = eidors_obj('fwd_model',sprintf('%s 2D',mdl3.name));
0512
0513
0514 [bdy,idx] = find_boundary(mdl3.elems);
0515 vtx = mdl3.nodes;
0516 z_vtx = reshape(vtx(bdy,3), size(bdy) );
0517 z_vtx_thres = max(z_vtx(:))-10*eps*range(z_vtx(:));
0518 lay0 = find( all(z_vtx >= z_vtx_thres, 2) );
0519 bdy0 = bdy( lay0, :);
0520
0521 vtx0 = unique(bdy0(:));
0522 mdl2.nodes = vtx(vtx0,1:2);
0523
0524
0525 nmap = zeros(size(vtx,1),1); nmap(vtx0) = 1:length(vtx0);
0526 bdy0 = reshape(nmap(bdy0), size(bdy0) );
0527 mdl2.elems = bdy0;
0528
0529
0530 mdl2.boundary = find_boundary( mdl2.elems);
0531
0532
0533 mdl2.gnd_node = nmap(mdl3.gnd_node);
0534
0535
0536
0537 idx2 = {};
0538 idx0 = idx( lay0, :);
0539 for i=1:size(idx3,2)
0540 idx2{i} = [];
0541 ii = 1;
0542 for j=1:size(idx3{i},1)
0543 idx_tmp = find( idx0==idx3{i}(j) );
0544 if not(isempty(idx_tmp))
0545 idx2{i}(ii,1) = idx_tmp(1,1);
0546 ii = ii + 1;
0547 end
0548 end
0549 end
0550
0551
0552 if isfield(mdl3,'electrode')
0553 mdl2.electrode = mdl3.electrode;
0554 for i=1:length(mdl2.electrode);
0555 nn = mdl3.nodes(mdl3.electrode(i).nodes,:);
0556 enodes = nmap( mdl2.electrode(i).nodes );
0557 enodes(enodes==0) = [];
0558 mdl2.electrode(i).nodes = enodes(:)';
0559 end
0560 end
0561
0562 ignore = {'electrode', 'nodes', 'boundary', 'elems', 'gnd_node', 'boundary_numbers', 'mat_idx'};
0563 for n=fieldnames(mdl3)'
0564 if ~any(strcmp(n,ignore))
0565 mdl2.(n{:}) = mdl3.(n{:});
0566 end
0567 end
0568
0569 function mdl = convert_cem2pem(mdl, xyzc)
0570 if ~isfield(mdl, 'electrode')
0571 return;
0572 end
0573 nd = size(mdl.nodes,2);
0574 for i=1:length(mdl.electrode)
0575 en = mdl.electrode(i).nodes;
0576 nn = mdl.nodes(en,:);
0577 if nd == 2
0578 np = xyzc(i,[1 3]);
0579 else
0580 np = xyzc(i,:);
0581 end
0582 D = pdist([np; nn]);
0583 [~,idx] = min(D(1,2:end));
0584 mdl.electrode(i).nodes = en(idx);
0585
0586
0587
0588 end
0589
0590 function D2 = pdist(X)
0591 if nargin == 2; error('only supports Euclidean distances'); end
0592
0593 D2 = bsxfun(@minus, X, permute(X,[3 2 1]));
0594 D2 = squeeze(sqrt(sum(D2.^2,2)));
0595
0596 function [mdl, c] = shift_electrode_positions(mdl, dx)
0597 for i = 1:length(mdl.electrode)
0598 en = mdl.electrode(i).nodes;
0599 ex = mdl.nodes(en,:);
0600 ep(i,:) = (max(ex,[],1) + min(ex,[],1))/2;
0601 end
0602 [mdl, c] = correct_electrode_positions(mdl, ep + dx);
0603
0604 function [mdl, c] = correct_electrode_positions(mdl, xyzc)
0605
0606 nd = size(mdl.nodes,2);
0607 c = 0; err = 1;
0608 while max(err) > eps
0609 for n = 1:nd
0610 switch(n)
0611 case 1
0612 mdl = mash_nodes(mdl, 'shift_all', 1, 1, xyzc);
0613 case 2
0614 mdl = mash_nodes(mdl, 'shift_surface', 1, nd, xyzc);
0615 case 3
0616 mdl = mash_nodes(mdl, 'shift_middle', 1, 2, xyzc);
0617 otherwise
0618 error('duh!');
0619 end
0620 end
0621 err = sqrt(sum(mdl_elec_err(mdl, xyzc).^2,2));
0622 c=c+1;
0623 if c >= 100
0624 break;
0625 end
0626 end
0627
0628 function mdl = mash_nodes(mdl, method, idm, dim, elec_true)
0629 elec_err = mdl_elec_err(mdl, elec_true);
0630 err = elec_err(:,dim);
0631
0632
0633
0634 xq = mdl.nodes(:,idm);
0635 x = [min(xq); ...
0636 mean([min(xq) min(elec_true(:,idm))]); ...
0637 elec_true(:,idm);
0638 mean([max(xq) max(elec_true(:,idm))]); ...
0639 max(xq)];
0640 v = [0; 0; err; 0; 0];
0641
0642
0643 interp_method = 'linear';
0644 if strcmp(method, 'shift_surface')
0645 interp_method = 'pchip';
0646 end
0647 vq = interp1(x, v, xq, interp_method, 'extrap');
0648
0649 switch method
0650 case 'shift_all'
0651 vqs = 1;
0652 case 'shift_middle'
0653 yq = mdl.nodes(:,dim);
0654 yqr = max(yq)-min(yq);
0655 yqm = (max(yq) + min(yq))/2;
0656 vqs = 1-abs(yq-yqm)./(yqr/2);
0657 case 'shift_surface'
0658 yq = mdl.nodes(:,dim);
0659 yqr = max(yq) - min(yq);
0660 yqm = min(yq);
0661 vqs = abs(yq - yqm)./yqr;
0662 otherwise
0663 error(['unrecognized method: ',method]);
0664 end
0665 mdl.nodes(:,dim) = mdl.nodes(:,dim) + (vq .* vqs);
0666
0667
0668 function err = mdl_elec_err(mdl, xyzc)
0669 if ~isfield(mdl, 'electrode')
0670 error('electrodes not available on this model, must supply positional error');
0671 end
0672
0673 nel=length(mdl.electrode);
0674 nd=size(mdl.nodes,2);
0675
0676 eu = ones(nel,nd)*NaN;
0677 for i=1:length(mdl.electrode)
0678 nn = mdl.nodes(mdl.electrode(i).nodes,:);
0679 eu(i,:) = (max(nn,[],1) + min(nn,[],1))./2;
0680 end
0681 err = xyzc(:,1:nd) - eu;
0682
0683 function do_unit_test
0684 ne = 16;
0685 imdl = mk_geophysics_model('h2p5a', ne);
0686 imdl.fwd_model.stimulation = stim_pattern_geophys(ne, 'Wenner');
0687 img = mk_image(imdl.fwd_model, 1);
0688 img.fwd_solve.get_all_meas = 1;
0689 vh = fwd_solve_halfspace(img);
0690 vd = fwd_solve(img);
0691 clf; h=plot([vh.meas vd.meas],'o--'); legend('analytic','FEM'); set(gca,'box','off'); set(h,'LineWidth',2);
0692
0693 imdl1 = mk_geophysics_model('h2a',[1:6]);
0694 imdl2 = mk_geophysics_model('h2a',[1:6]');
0695 imdl3 = mk_geophysics_model('h2a',[1:6]'*[1 0]);
0696 imdl3Hnm2d = mk_geophysics_model('H2a',[1:6],{'threshold',Inf});
0697 imdl3Hnm3d = mk_geophysics_model('H3a',[1:6],{'threshold',Inf});
0698 imdl4 = mk_geophysics_model('h2a',[1:6]'*[1 0] + ([1:6]*0+2)'*[0 1]);
0699 R = @(x) [cosd(x) -sind(x); sind(x) cosd(x)];
0700 X = [0 2];
0701 imdl5 = mk_geophysics_model('h2a',([1:6]'*[1 0] + ([1:6]*0+1)'*X)*R(-135));
0702 elec_pos_2d = [1 1; 2 2; 3 1; 4 1.5];
0703 elec_pos_3d = [1 0 0; 2 0.5 1; 3 -0.5 2.5; 10 0 3];
0704 imdl2dc = mk_geophysics_model('h2a', elec_pos_2d);
0705 imdl3dc = mk_geophysics_model('h3a', elec_pos_3d);
0706 imdl2dp = mk_geophysics_model('H2a', elec_pos_2d);
0707 imdl3dp = mk_geophysics_model('H3a', elec_pos_3d);
0708 imdl2df = mk_geophysics_model('H2a', elec_pos_2d, {'threshold', Inf});
0709 imdl3df = mk_geophysics_model('H3a', elec_pos_3d, {'threshold', Inf});
0710
0711
0712 imdlh32 = mk_geophysics_model('h32a', elec_pos_3d);
0713 imdlh22 = mk_geophysics_model('h22a', elec_pos_2d);
0714 imdlH32 = mk_geophysics_model('H32a', elec_pos_3d);
0715 imdlH22 = mk_geophysics_model('H22a', elec_pos_2d);
0716
0717
0718 imdlh32_16 = mk_geophysics_model('h32a', 16);
0719 imdlh22_16 = mk_geophysics_model('h22a', 16);
0720 imdlH32_16 = mk_geophysics_model('H32a', 16);
0721 imdlH22_16 = mk_geophysics_model('H22a', 16);
0722
0723 if 0
0724
0725 [yy,xx] = meshgrid(0:3:24, [0 4:2:20*2 20*2+4]);
0726 xyz = [xx(:) yy(:) zeros(length(yy(:)),1)];
0727
0728
0729
0730
0731 [yy,xx]=meshgrid(1:3,1:3); xyz = [xx(:) yy(:) zeros(length(yy(:)),1)];
0732 imdl = mk_geophysics_model('H3a',xyz);
0733
0734 end
0735
0736 unit_test_cmp('h2a halfspace vs default TEST', norm(vh.meas - vd.meas), 0, 4e-3);
0737 unit_test_cmp('1d elec list equivalence (row/col)',unit_test_elec_pos(imdl1), unit_test_elec_pos(imdl2));
0738 unit_test_cmp('1d vs. 2d elec list equivalence',unit_test_elec_pos(imdl1), unit_test_elec_pos(imdl3));
0739 unit_test_cmp('2D PEM w/o node mashing, no vertical relief',[1:6]'*[1 0], unit_test_elec_pos(imdl3Hnm2d), eps);
0740 unit_test_cmp('2D PEM *is* PEM',length(imdl3Hnm2d.fwd_model.electrode(1).nodes),1)
0741 unit_test_cmp('3D PEM w/o node mashing, no vertical relief',[1:6]'*[1 0 0], unit_test_elec_pos(imdl3Hnm3d), eps);
0742 unit_test_cmp('3D PEM *is* PEM',length(imdl3Hnm3d.fwd_model.electrode(1).nodes),1)
0743 unit_test_cmp('1d vs. 2d + y=2 elec list equivalence',unit_test_elec_pos(imdl1), unit_test_elec_pos(imdl4)-([1:6]*0+2)'*[0 1]);
0744 unit_test_cmp('1d vs. 2d + y=2 - 135 deg elec eq', ...
0745 unit_test_elec_pos(imdl1), ...
0746 unit_test_elec_pos(imdl5, R(135), -X), eps*10);
0747 unit_test_cmp('2d with vertical geometry (mash nodes) CEM', ...
0748 elec_pos_2d, ...
0749 unit_test_elec_pos(imdl2dc), 0.01);
0750 unit_test_cmp('3d with vertical geometry (mash nodes) CEM', ...
0751 elec_pos_3d, ...
0752 unit_test_elec_pos(imdl3dc), 0.001);
0753 unit_test_cmp('2d with vertical geometry (mash nodes) PEM', ...
0754 elec_pos_2d, ...
0755 unit_test_elec_pos(imdl2dp), 10*eps);
0756 unit_test_cmp('3d with vertical geometry (mash nodes) PEM', ...
0757 elec_pos_3d, ...
0758 unit_test_elec_pos(imdl3dp), 10*eps);
0759 unit_test_cmp('2d with vertical geometry (w/o mash nodes) PEM', ...
0760 elec_pos_2d, ...
0761 unit_test_elec_pos(imdl2df), -10*eps);
0762 unit_test_cmp('3d with vertical geometry (w/o mash nodes) PEM', ...
0763 elec_pos_3d, ...
0764 unit_test_elec_pos(imdl3df), -10*eps);
0765
0766 unit_test_cmp('h32a dual 3d/2d', ...
0767 elec_pos_3d, ...
0768 unit_test_elec_pos(imdlh32), 0.001);
0769 unit_test_cmp('H32a dual 3d/2d', ...
0770 elec_pos_3d, ...
0771 unit_test_elec_pos(imdlH32), 10*eps);
0772 unit_test_cmp('h22a dual 2d/2d', ...
0773 elec_pos_2d, ...
0774 unit_test_elec_pos(imdlh22), 0.002);
0775 unit_test_cmp('H22a dual 2d/2d', ...
0776 elec_pos_2d, ...
0777 unit_test_elec_pos(imdlH22), 10*eps);
0778
0779 clf; subplot(221); show_fem(imdl1.fwd_model); title('models match? A');
0780 subplot(222); show_fem(imdl5.fwd_model); title('models match? C');
0781 subplot(223); show_fem(imdl2dc.fwd_model); title('2d deformations');
0782 subplot(224); show_fem(imdl3dc.fwd_model); title('3d deformations'); view([0 -1 0.01]);
0783
0784 if 1
0785 clf; subplot(221); show_fem(imdlh22.fwd_model);
0786 subplot(222); show_fem(imdlh32.fwd_model);
0787 subplot(223); show_fem(imdlH22.fwd_model);
0788 subplot(224); show_fem(imdlH32.fwd_model);
0789 end
0790
0791 if 0
0792 clf; subplot(221); show_fem(imdlh22.rec_model);
0793 subplot(222); show_fem(imdlh32.rec_model);
0794 subplot(223); show_fem(imdlH22.rec_model);
0795 subplot(224); show_fem(imdlH32.rec_model);
0796 end
0797
0798 imdl = mk_geophysics_model('h2p5a', ne, {'extra_ng_code', 'solid tt = orthobrick(-1,-1,-1;-0.5,0,-0.5);\ntlo tt;\n'});
0799 clf; show_fem(imdl.fwd_model);
0800
0801
0802 function xyz = unit_test_elec_pos(imdl, R, X)
0803 if nargin < 2; R = 1; end
0804 if nargin < 3; X = 0; end
0805 fmdl = imdl.fwd_model;
0806 xyz = zeros(length(fmdl.electrode),size(fmdl.nodes,2))*NaN;
0807 for i = 1:length(fmdl.electrode)
0808 nn = fmdl.nodes(fmdl.electrode(i).nodes,:);
0809 xyz(i,:) = (max(nn,[],1) + min(nn,[],1))/2;
0810 xyz(i,:) = xyz(i,:)*R + X;
0811 end