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