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 6521 2022-12-30 21:33:17Z bgrychtol $';
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 end
0301 end
0302
0303 if cem2pem
0304 fmdl = convert_cem2pem(fmdl, xyzc);
0305 end
0306
0307
0308 xllim = fmdlbox(1,1);
0309 xrlim = fmdlbox(1,2);
0310 zdepth = fmdlbox(3,1);
0311
0312 xr=max(floor((xrlim-xllim)/hmax_rec),1)*2+1;
0313 yr=max(floor(-zdepth/hmax_rec),1)*2+1;
0314 [x,y] = meshgrid( linspace(xllim,xrlim,xr), linspace(zdepth,0,yr));
0315 vtx= [x(:),y(:)];
0316 if CMDL_DIM ~= 0
0317 cmdl= mk_fmdl_from_nodes( vtx,{vtx(1,:)}, z_contact, 'sq_m2');
0318 end
0319
0320
0321 for i=1:ne
0322 n=fmdl.electrode(i).nodes;
0323 nn=length(n);
0324 nx=fmdl.nodes(n,:);
0325
0326 fmdl.electrode(i).z_contact = z_contact;
0327 if CMDL_DIM ~= 0
0328 nnc = length(cmdl.nodes);
0329 cmdl.nodes = [cmdl.nodes; nx(:,[1 FMDL_DIM])];
0330 cmdl.electrode(i).nodes = (nnc+1):(nnc+nn);
0331 cmdl.electrode(i).z_contact = z_contact;
0332 end
0333 end
0334
0335
0336 elec_err = sqrt(sum(mdl_elec_err(fmdl, xyzc).^2,2));
0337 if max(elec_err) > threshold
0338 [fmdl, cf] = correct_electrode_positions(fmdl, xyzc);
0339
0340 if CMDL_DIM ~= 0
0341 [cmdl, cc] = correct_electrode_positions(cmdl, xyzc);
0342 end
0343 end
0344
0345 fmdl.mv_elec = @shift_electrode_positions;
0346
0347
0348 nn = size(fmdl.nodes,1);
0349 Xn = repmat(X(1,:), nn, 1);
0350 fmdl.nodes = ([fmdl.nodes zeros(nn,3-FMDL_DIM)]/ R) + Xn;
0351 fmdl.nodes = fmdl.nodes(:,1:FMDL_DIM);
0352
0353 if CMDL_DIM ~= 0
0354 nn = size(cmdl.nodes,1);
0355 Xn = repmat(X(1,:), nn, 1);
0356 if CMDL_DIM ~= FMDL_DIM
0357 cmdl.nodes = ([cmdl.nodes(:,1) zeros(nn,1) cmdl.nodes(:,2:end)]/ R) + Xn;
0358 cmdl.nodes = cmdl.nodes(:,[1 3]);
0359
0360 else
0361 cmdl.nodes = ([cmdl.nodes zeros(nn,3-CMDL_DIM)]/ R) + Xn;
0362 cmdl.nodes = cmdl.nodes(:,1:CMDL_DIM);
0363 end
0364 end
0365
0366
0367 for i = 1:length(fmdl.electrode)
0368 nn = fmdl.electrode(i).nodes;
0369 assert(length(nn(:)) > 0, sprintf('electrode#%d: failed to find nodes',i));
0370 end
0371
0372
0373
0374
0375 [~, gn] = min(fmdl.nodes(:,end));
0376 fmdl.gnd_node = gn;
0377 if CMDL_DIM ~= 0
0378 [~, gn] = min(cmdl.nodes(:,end));
0379 cmdl.gnd_node = gn;
0380 end
0381
0382
0383 imdl= mk_common_model('a2d0c',4);
0384 imdl.fwd_model = fmdl;
0385 imdl.name = ['EIDORS mk_geophysics_model ' str];
0386 imdl.fwd_model.name = ['EIDORS mk_geophysics_model fwd model ' str];
0387 if CMDL_DIM ~= 0
0388 imdl.rec_model.name = ['EIDORS mk_geophysics_model rec model ' str];
0389 imdl.rec_model = cmdl;
0390
0391
0392
0393
0394 if ~skip_c2f
0395 [c2f,out] = mk_approx_c2f(fmdl,cmdl);
0396 imdl.fwd_model.coarse2fine = c2f;
0397 imdl.fwd_model.background = out;
0398 end
0399 end
0400
0401 if MDL_2p5D_CONFIG
0402 imdl.fwd_model.jacobian = @jacobian_adjoint_2p5d_1st_order;
0403 imdl.fwd_model.solve = @fwd_solve_2p5d_1st_order;
0404 imdl.fwd_model.system_mat = @system_mat_2p5d_1st_order;
0405 imdl.fwd_model.jacobian_adjoint_2p5d_1st_order.k = [0 3];
0406 imdl.fwd_model.fwd_solve_2p5d_1st_order.k = [0 3];
0407 end
0408
0409 if ~strcmp(build_stim,'none')
0410 imdl.fwd_model.stimulation = stim_pattern_geophys(length(imdl.fwd_model.electrode), build_stim);
0411 end
0412
0413 if save_model_to_disk
0414 save(filename,'imdl','SALT');
0415 end
0416
0417
0418
0419
0420 function s = a2s(a)
0421 if length(a(:)) == 3
0422 s = sprintf('%f,%f,%f', ...
0423 a(1), a(2), a(3));
0424 else
0425 s = sprintf('%f,%f,%f;%f,%f,%f', ...
0426 a(1,1), a(1,2), a(1,3), ...
0427 a(2,1), a(2,2), a(2,3));
0428 end
0429
0430
0431
0432 function [R,X,var] = rot_line_to_xaxis(xyz)
0433 x = xyz(:,1); y=xyz(:,2); z=xyz(:,3);
0434
0435
0436
0437 p = mean(xyz);
0438 [U,S,V] = svd([x-p(1), y-p(2), z-p(3)]);
0439 if V(end,1) ~= 0
0440 N=1/V(end,1)*V(:,1);
0441 else
0442 N=V(:,1);
0443 end
0444 A=p' + dot( xyz(1, :) - p, N ) * N/norm(N)^2;
0445 B=p' + dot( xyz(end,:) - p, N ) * N/norm(N)^2;
0446
0447
0448 a = N/norm(N);
0449 b = [ 1 0 0 ]';
0450
0451 v = cross(a, b);
0452 s = norm(v); c = dot(a, b);
0453 xt = @(v) [ 0 -v(3) v(2); ...
0454 v(3) 0 -v(1); ...
0455 -v(2) v(1) 0];
0456 if abs(s) < eps*1e3
0457 R = eye(3);
0458 else
0459 R = (eye(3) + xt(v) + xt(v)^2 * (1-c)/s^2);
0460 R=R';
0461 end
0462
0463 X = repmat(p, size(xyz,1), 1);
0464 R = R / max(range((xyz-X)*R)) * 2;
0465
0466 DEBUG=0;
0467 if DEBUG
0468 clf;
0469 subplot(211);
0470 plot3(x,y,z,'bo');
0471 hold on;
0472 plot3(x(1),y(1),z(1),'go');
0473 plot3(p(1),p(2),p(3),'ro');
0474 plot3([A(1),B(1)],[A(2),B(2)],[A(3),B(3)]);
0475 grid; axis equal; hold off;
0476 xlabel('x'); ylabel('y'); zlabel('z');
0477 title('pre-rotation');
0478 subplot(212);
0479 xx = (xyz-X)*R;
0480 plot3(xx(:,1), xx(:,2), xx(:,3),'bo');
0481 hold on;
0482 plot3(xx(1,1), xx(1,2), xx(1,3),'go');
0483 plot3(0,0,0,'ro');
0484 grid; axis equal; hold off;
0485 xlabel('x'); ylabel('y'); zlabel('z');
0486 title('post-rotation');
0487 end
0488
0489
0490 function r = range(a)
0491 r = max(a(:))-min(a(:));
0492
0493 function [mdl2,idx2] = copy_mdl2d_from3d(mdl3,idx3,sel);
0494
0495
0496 if sel == 'x'
0497
0498 T = [ 0 0 1; 0 1 0; 1 0 0 ];
0499 elseif sel == 'y'
0500
0501 T = [ 1 0 0; 0 0 1; 0 1 0 ];
0502 elseif sel == 'z'
0503 T = eye(3);
0504 else
0505 error('sel must be "x", "y" or "z"');
0506 end
0507 mdl3.nodes = mdl3.nodes * T;
0508
0509
0510 mdl2 = eidors_obj('fwd_model',sprintf('%s 2D',mdl3.name));
0511
0512
0513 [bdy,idx] = find_boundary(mdl3.elems);
0514 vtx = mdl3.nodes;
0515 z_vtx = reshape(vtx(bdy,3), size(bdy) );
0516 z_vtx_thres = max(z_vtx(:))-10*eps*range(z_vtx(:));
0517 lay0 = find( all(z_vtx >= z_vtx_thres, 2) );
0518 bdy0 = bdy( lay0, :);
0519
0520 vtx0 = unique(bdy0(:));
0521 mdl2.nodes = vtx(vtx0,1:2);
0522
0523
0524 nmap = zeros(size(vtx,1),1); nmap(vtx0) = 1:length(vtx0);
0525 bdy0 = reshape(nmap(bdy0), size(bdy0) );
0526 mdl2.elems = bdy0;
0527
0528
0529 mdl2.boundary = find_boundary( mdl2.elems);
0530
0531
0532 mdl2.gnd_node = nmap(mdl3.gnd_node);
0533
0534
0535
0536 idx2 = {};
0537 idx0 = idx( lay0, :);
0538 for i=1:size(idx3,2)
0539 idx2{i} = [];
0540 ii = 1;
0541 for j=1:size(idx3{i},1)
0542 idx_tmp = find( idx0==idx3{i}(j) );
0543 if not(isempty(idx_tmp))
0544 idx2{i}(ii,1) = idx_tmp(1,1);
0545 ii = ii + 1;
0546 end
0547 end
0548 end
0549
0550
0551 if isfield(mdl3,'electrode')
0552 mdl2.electrode = mdl3.electrode;
0553 for i=1:length(mdl2.electrode);
0554 nn = mdl3.nodes(mdl3.electrode(i).nodes,:);
0555 enodes = nmap( mdl2.electrode(i).nodes );
0556 enodes(enodes==0) = [];
0557 mdl2.electrode(i).nodes = enodes(:)';
0558 end
0559 end
0560
0561 ignore = {'electrode', 'nodes', 'boundary', 'elems', 'gnd_node', 'boundary_numbers', 'mat_idx'};
0562 for n=fieldnames(mdl3)'
0563 if ~any(strcmp(n,ignore))
0564 mdl2.(n{:}) = mdl3.(n{:});
0565 end
0566 end
0567
0568 function mdl = convert_cem2pem(mdl, xyzc)
0569 if ~isfield(mdl, 'electrode')
0570 return;
0571 end
0572 nd = size(mdl.nodes,2);
0573 for i=1:length(mdl.electrode)
0574 en = mdl.electrode(i).nodes;
0575 nn = mdl.nodes(en,:);
0576 if nd == 2
0577 np = xyzc(i,[1 3]);
0578 else
0579 np = xyzc(i,:);
0580 end
0581 D = pdist([np; nn]);
0582 [~,idx] = min(D(1,2:end));
0583 mdl.electrode(i).nodes = en(idx);
0584
0585
0586
0587 end
0588
0589 function D2 = pdist(X)
0590 if nargin == 2; error('only supports Euclidean distances'); end
0591
0592 D2 = bsxfun(@minus, X, permute(X,[3 2 1]));
0593 D2 = squeeze(sqrt(sum(D2.^2,2)));
0594
0595 function [mdl, c] = shift_electrode_positions(mdl, dx)
0596 for i = 1:length(mdl.electrode)
0597 en = mdl.electrode(i).nodes;
0598 ex = mdl.nodes(en,:);
0599 ep(i,:) = (max(ex,[],1) + min(ex,[],1))/2;
0600 end
0601 [mdl, c] = correct_electrode_positions(mdl, ep + dx);
0602
0603 function [mdl, c] = correct_electrode_positions(mdl, xyzc)
0604
0605 nd = size(mdl.nodes,2);
0606 c = 0; err = 1;
0607 while max(err) > eps
0608 for n = 1:nd
0609 switch(n)
0610 case 1
0611 mdl = mash_nodes(mdl, 'shift_all', 1, 1, xyzc);
0612 case 2
0613 mdl = mash_nodes(mdl, 'shift_surface', 1, nd, xyzc);
0614 case 3
0615 mdl = mash_nodes(mdl, 'shift_middle', 1, 2, xyzc);
0616 otherwise
0617 error('duh!');
0618 end
0619 end
0620 err = sqrt(sum(mdl_elec_err(mdl, xyzc).^2,2));
0621 c=c+1;
0622 if c >= 100
0623 break;
0624 end
0625 end
0626
0627 function mdl = mash_nodes(mdl, method, idm, dim, elec_true)
0628 elec_err = mdl_elec_err(mdl, elec_true);
0629 err = elec_err(:,dim);
0630
0631
0632
0633 xq = mdl.nodes(:,idm);
0634 x = [min(xq); ...
0635 mean([min(xq) min(elec_true(:,idm))]); ...
0636 elec_true(:,idm);
0637 mean([max(xq) max(elec_true(:,idm))]); ...
0638 max(xq)];
0639 v = [0; 0; err; 0; 0];
0640
0641
0642 interp_method = 'linear';
0643 if strcmp(method, 'shift_surface')
0644 interp_method = 'pchip';
0645 end
0646 vq = interp1(x, v, xq, interp_method, 'extrap');
0647
0648 switch method
0649 case 'shift_all'
0650 vqs = 1;
0651 case 'shift_middle'
0652 yq = mdl.nodes(:,dim);
0653 yqr = max(yq)-min(yq);
0654 yqm = (max(yq) + min(yq))/2;
0655 vqs = 1-abs(yq-yqm)./(yqr/2);
0656 case 'shift_surface'
0657 yq = mdl.nodes(:,dim);
0658 yqr = max(yq) - min(yq);
0659 yqm = min(yq);
0660 vqs = abs(yq - yqm)./yqr;
0661 otherwise
0662 error(['unrecognized method: ',method]);
0663 end
0664 mdl.nodes(:,dim) = mdl.nodes(:,dim) + (vq .* vqs);
0665
0666
0667 function err = mdl_elec_err(mdl, xyzc)
0668 if ~isfield(mdl, 'electrode')
0669 error('electrodes not available on this model, must supply positional error');
0670 end
0671
0672 nel=length(mdl.electrode);
0673 nd=size(mdl.nodes,2);
0674
0675 eu = ones(nel,nd)*NaN;
0676 for i=1:length(mdl.electrode)
0677 nn = mdl.nodes(mdl.electrode(i).nodes,:);
0678 eu(i,:) = (max(nn,[],1) + min(nn,[],1))./2;
0679 end
0680 err = xyzc(:,1:nd) - eu;
0681
0682 function test_fwd_rec_match(imdl,idx,str)
0683 fwd = imdl.fwd_model.nodes(:,idx);
0684 mxf = max(fwd); mnf = min(fwd);
0685 rec = imdl.rec_model.nodes;
0686 mxr = max(rec); mnr = min(fwd);
0687 unit_test_cmp(['fwd-rec match: ',str],[mxf,mnf],[mxr,mnr],10*eps);
0688
0689 function do_unit_test
0690 ne = 16;
0691 imdl = mk_geophysics_model('h2p5a', ne);
0692 imdl.fwd_model.stimulation = stim_pattern_geophys(ne, 'Wenner');
0693 img = mk_image(imdl.fwd_model, 1);
0694 img.fwd_solve.get_all_meas = 1;
0695 vh = fwd_solve_halfspace(img);
0696 vd = fwd_solve(img);
0697 clf; h=plot([vh.meas vd.meas],'o--'); legend('analytic','FEM'); set(gca,'box','off'); set(h,'LineWidth',2);
0698
0699 imdl1 = mk_geophysics_model('h2a',[1:6]);
0700 imdl2 = mk_geophysics_model('h2a',[1:6]');
0701 imdl3 = mk_geophysics_model('h2a',[1:6]'*[1 0]);
0702 imdl3Hnm2d = mk_geophysics_model('H2a',[1:6],{'threshold',Inf});
0703 imdl3Hnm3d = mk_geophysics_model('H3a',[1:6],{'threshold',Inf});
0704 imdl4 = mk_geophysics_model('h2a',[1:6]'*[1 0] + ([1:6]*0+2)'*[0 1]);
0705 R = @(x) [cosd(x) -sind(x); sind(x) cosd(x)];
0706 X = [0 2];
0707 imdl5 = mk_geophysics_model('h2a',([1:6]'*[1 0] + ([1:6]*0+1)'*X)*R(-135));
0708 elec_pos_2d = [1 1; 2 2; 3 1; 4 1.5];
0709 elec_pos_3d = [1 0 0; 2 0.5 1; 3 -0.5 2.5; 10 0 3];
0710 imdl2dc = mk_geophysics_model('h2a', elec_pos_2d);
0711 imdl3dc = mk_geophysics_model('h3a', elec_pos_3d);
0712 imdl2dp = mk_geophysics_model('H2a', elec_pos_2d);
0713 imdl3dp = mk_geophysics_model('H3a', elec_pos_3d);
0714 imdl2df = mk_geophysics_model('H2a', elec_pos_2d, {'threshold', Inf});
0715 imdl3df = mk_geophysics_model('H3a', elec_pos_3d, {'threshold', Inf});
0716
0717
0718 imdlh32 = mk_geophysics_model('h32a', elec_pos_3d);
0719 imdlh22 = mk_geophysics_model('h22a', elec_pos_2d);
0720 imdlH32 = mk_geophysics_model('H32a', elec_pos_3d);
0721 imdlH22 = mk_geophysics_model('H22a', elec_pos_2d);
0722
0723
0724 imdlh32_16 = mk_geophysics_model('h32a', 16);
0725 test_fwd_rec_match(imdlh32_16,[1,3],'h32a')
0726 imdlh22_16 = mk_geophysics_model('h22a', 16);
0727 test_fwd_rec_match(imdlh22_16,[1,2],'h22a')
0728 imdlH32_16 = mk_geophysics_model('H32a', 16);
0729 test_fwd_rec_match(imdlH32_16,[1,3],'h22a')
0730 imdlH22_16 = mk_geophysics_model('H22a', 16);
0731 test_fwd_rec_match(imdlH22_16,[1,2],'h22a')
0732
0733 if 0
0734
0735 [yy,xx] = meshgrid(0:3:24, [0 4:2:20*2 20*2+4]);
0736 xyz = [xx(:) yy(:) zeros(length(yy(:)),1)];
0737
0738
0739
0740
0741 [yy,xx]=meshgrid(1:3,1:3); xyz = [xx(:) yy(:) zeros(length(yy(:)),1)];
0742 imdl = mk_geophysics_model('H3a',xyz);
0743
0744 end
0745
0746 unit_test_cmp('h2a halfspace vs default TEST', norm(vh.meas - vd.meas), 0, 4e-3);
0747 unit_test_cmp('1d elec list equivalence (row/col)',unit_test_elec_pos(imdl1), unit_test_elec_pos(imdl2));
0748 unit_test_cmp('1d vs. 2d elec list equivalence',unit_test_elec_pos(imdl1), unit_test_elec_pos(imdl3));
0749 unit_test_cmp('2D PEM w/o node mashing, no vertical relief',[1:6]'*[1 0], unit_test_elec_pos(imdl3Hnm2d), eps);
0750 unit_test_cmp('2D PEM *is* PEM',length(imdl3Hnm2d.fwd_model.electrode(1).nodes),1)
0751 unit_test_cmp('3D PEM w/o node mashing, no vertical relief',[1:6]'*[1 0 0], unit_test_elec_pos(imdl3Hnm3d), eps);
0752 unit_test_cmp('3D PEM *is* PEM',length(imdl3Hnm3d.fwd_model.electrode(1).nodes),1)
0753 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]);
0754 unit_test_cmp('1d vs. 2d + y=2 - 135 deg elec eq', ...
0755 unit_test_elec_pos(imdl1), ...
0756 unit_test_elec_pos(imdl5, R(135), -X), eps*10);
0757 unit_test_cmp('2d with vertical geometry (mash nodes) CEM', ...
0758 elec_pos_2d, ...
0759 unit_test_elec_pos(imdl2dc), 0.01);
0760 unit_test_cmp('3d with vertical geometry (mash nodes) CEM', ...
0761 elec_pos_3d, ...
0762 unit_test_elec_pos(imdl3dc), 0.001);
0763 unit_test_cmp('2d with vertical geometry (mash nodes) PEM', ...
0764 elec_pos_2d, ...
0765 unit_test_elec_pos(imdl2dp), 10*eps);
0766 unit_test_cmp('3d with vertical geometry (mash nodes) PEM', ...
0767 elec_pos_3d, ...
0768 unit_test_elec_pos(imdl3dp), 10*eps);
0769 unit_test_cmp('2d with vertical geometry (w/o mash nodes) PEM', ...
0770 elec_pos_2d, ...
0771 unit_test_elec_pos(imdl2df), -10*eps);
0772 unit_test_cmp('3d with vertical geometry (w/o mash nodes) PEM', ...
0773 elec_pos_3d, ...
0774 unit_test_elec_pos(imdl3df), -10*eps);
0775
0776 unit_test_cmp('h32a dual 3d/2d', ...
0777 elec_pos_3d, ...
0778 unit_test_elec_pos(imdlh32), 0.001);
0779 unit_test_cmp('H32a dual 3d/2d', ...
0780 elec_pos_3d, ...
0781 unit_test_elec_pos(imdlH32), 10*eps);
0782 unit_test_cmp('h22a dual 2d/2d', ...
0783 elec_pos_2d, ...
0784 unit_test_elec_pos(imdlh22), 0.002);
0785 unit_test_cmp('H22a dual 2d/2d', ...
0786 elec_pos_2d, ...
0787 unit_test_elec_pos(imdlH22), 10*eps);
0788
0789 clf; subplot(221); show_fem(imdl1.fwd_model); title('models match? A');
0790 subplot(222); show_fem(imdl5.fwd_model); title('models match? C');
0791 subplot(223); show_fem(imdl2dc.fwd_model); title('2d deformations');
0792 subplot(224); show_fem(imdl3dc.fwd_model); title('3d deformations'); view([0 -1 0.01]);
0793
0794 if 1
0795 clf; subplot(221); show_fem(imdlh22.fwd_model);
0796 subplot(222); show_fem(imdlh32.fwd_model);
0797 subplot(223); show_fem(imdlH22.fwd_model);
0798 subplot(224); show_fem(imdlH32.fwd_model);
0799 end
0800
0801 if 0
0802 clf; subplot(221); show_fem(imdlh22.rec_model);
0803 subplot(222); show_fem(imdlh32.rec_model);
0804 subplot(223); show_fem(imdlH22.rec_model);
0805 subplot(224); show_fem(imdlH32.rec_model);
0806 end
0807
0808 imdl = mk_geophysics_model('h2p5a', ne, {'extra_ng_code', 'solid tt = orthobrick(-1,-1,-1;-0.5,0,-0.5);\ntlo tt;\n'});
0809 clf; show_fem(imdl.fwd_model);
0810
0811
0812 function xyz = unit_test_elec_pos(imdl, R, X)
0813 if nargin < 2; R = 1; end
0814 if nargin < 3; X = 0; end
0815 fmdl = imdl.fwd_model;
0816 xyz = zeros(length(fmdl.electrode),size(fmdl.nodes,2))*NaN;
0817 for i = 1:length(fmdl.electrode)
0818 nn = fmdl.nodes(fmdl.electrode(i).nodes,:);
0819 xyz(i,:) = (max(nn,[],1) + min(nn,[],1))/2;
0820 xyz(i,:) = xyz(i,:)*R + X;
0821 end