0001 function data =fwd_solve_1st_order(fwd_model, img)
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 if ischar(fwd_model) && strcmp(fwd_model,'UNIT_TEST'); do_unit_test; return; end
0030
0031 if nargin == 1
0032 img= fwd_model;
0033 elseif strcmp(getfield(warning('query','EIDORS:DeprecatedInterface'),'state'),'on')
0034 warning('EIDORS:DeprecatedInterface', ...
0035 ['Calling FWD_SOLVE_1ST_ORDER with two arguments is deprecated and will cause' ...
0036 ' an error in a future version. First argument ignored.']);
0037 end
0038 fwd_model= img.fwd_model;
0039
0040 img = data_mapper(img);
0041 if ~ismember(img.current_params, supported_params)
0042 error('EIDORS:PhysicsNotSupported', '%s does not support %s', ...
0043 'FWD_SOLVE_1ST_ORDER',img.current_params);
0044 end
0045
0046 img = convert_img_units(img, 'conductivity');
0047
0048 pp= fwd_model_parameters( fwd_model, 'skip_VOLUME' );
0049 pp = set_gnd_node(fwd_model, pp);
0050 s_mat= calc_system_mat( img );
0051
0052 if isfield(fwd_model,'model_reduction')
0053 [s_mat.E, main_idx, pp] = mdl_reduction(s_mat.E, ...
0054 img.fwd_model.model_reduction, img, pp );
0055 else
0056 pp.mr_mapper = 1:size(s_mat.E,1);
0057 end
0058
0059
0060
0061
0062
0063
0064 [dirichlet_nodes, dirichlet_values, neumann_nodes, has_gnd_node]= ...
0065 find_dirichlet_nodes( fwd_model, pp );
0066
0067 v= full(horzcat(dirichlet_values{:}));
0068 for i=1:length(dirichlet_nodes)
0069 idx= 1:size(s_mat.E,1);
0070 idx( dirichlet_nodes{i} ) = [];
0071
0072 if length(dirichlet_nodes) == 1; rhs = 1:size(pp.QQ,2);
0073 else ; rhs = i; end
0074 v(idx,rhs)= left_divide( s_mat.E(idx,idx), ...
0075 neumann_nodes{i}(idx,:) - ...
0076 s_mat.E(idx,:)*dirichlet_values{i},fwd_model);
0077 end
0078
0079
0080 if has_gnd_node
0081 Ignd = s_mat.E(dirichlet_nodes{1},:)*v;
0082 Irel = Ignd./sum(abs(pp.QQ));
0083 if max(abs(Irel))>1e-6
0084 eidors_msg('%4.5f%% of current is flowing through ground node. Check stimulation pattern', max(abs(Irel))*100,1);
0085 end
0086 end
0087
0088
0089
0090
0091
0092 idx = find(any(pp.N2E));
0093 v_els= pp.N2E(:,idx) * v(idx,:);
0094
0095
0096
0097 data.meas= meas_from_v_els(v_els, pp);
0098 data.time= NaN;
0099 data.name= 'solved by fwd_solve_1st_order';
0100 try; if img.fwd_solve.get_all_meas == 1
0101 outmap = pp.mr_mapper(1:pp.n_node);
0102 data.volt(outmap,:) = v(1:pp.n_node,:);
0103 end; end
0104 try; if img.fwd_solve.get_all_nodes== 1
0105 data.volt(pp.mr_mapper,:) = v;
0106 end; end
0107 try; if img.fwd_solve.get_elec_curr== 1
0108
0109 idx = find(any(pp.N2E));
0110 data.elec_curr = pp.N2E(:,idx) * s_mat.E(idx,:) * v;
0111 end; end
0112
0113
0114
0115 function [dirichlet_nodes, dirichlet_values, neumann_nodes, has_gnd_node]= ...
0116 find_dirichlet_nodes( fwd_model, pp );
0117 fnanQQ = isnan(pp.QQ);
0118 lstims = size(pp.QQ,2);
0119
0120 if any(any(fnanQQ))
0121 has_gnd_node = 0;
0122
0123
0124
0125
0126 if ~any(any(fnanQQ(:,1)*ones(1,lstims) - fnanQQ,2))
0127 dirichlet_nodes{1} = find(fnanQQ(:,1));
0128 dirichlet_values{1} = sparse(size(pp.N2E,2), size(fnanQQ,2));
0129 dirichlet_values{1}(fnanQQ) = pp.VV(fnanQQ);
0130 neumann_nodes{1} = pp.QQ;
0131 neumann_nodes{1}(fnanQQ) = 0;
0132 else
0133 for i=1:size(fnanQQ,2)
0134 fnanQQi= fnanQQ(:,i);
0135 if any(fnanQQi)
0136 dirichlet_nodes{i} = find(fnanQQi);
0137 dirichlet_values{i} = sparse(size(pp.N2E,2), 1);
0138 dirichlet_values{i}(fnanQQi) = pp.VV(fnanQQi,i);
0139 neumann_nodes{i} = pp.QQ(:,i);
0140 neumann_nodes{i}(fnanQQi) = 0;
0141 elseif isfield(pp,'gnd_node')
0142 dirichlet_nodes{i} = pp.gnd_node;
0143 dirichlet_values{i} = sparse(size(pp.N2E,2), 1);
0144 neumann_nodes{1} = pp.QQ(:,i);
0145 has_gnd_node= 1;
0146 else
0147 error('no required ground node on model');
0148 end
0149 end
0150 end
0151 elseif isfield(pp,'gnd_node')
0152 dirichlet_nodes{1} = pp.gnd_node;
0153 dirichlet_values{1} = sparse(size(pp.N2E,2), size(fnanQQ,2));
0154 neumann_nodes{1} = pp.QQ;
0155 has_gnd_node= 1;
0156 else
0157 error('no required ground node on model');
0158 end
0159
0160 function pp = set_gnd_node(fwd_model, pp);
0161 if isfield(fwd_model,'gnd_node');
0162 pp.gnd_node = fwd_model.gnd_node;
0163 else
0164
0165 ctr = mean(fwd_model.nodes,1);
0166 d2 = sum(bsxfun(@minus,fwd_model.nodes,ctr).^2,2);
0167 [~,pp.gnd_node] = min(d2);
0168 eidors_msg('Warning: no ground node found: choosing node %d',pp.gnd_node(1),1);
0169 end
0170
0171 function vv = meas_from_v_els( v_els, pp)
0172 try
0173
0174
0175
0176
0177
0178
0179
0180
0181
0182
0183
0184
0185
0186
0187
0188 [n_elec,n_stim] = size(v_els);
0189
0190 vv = pp.v2meas * v_els(:);
0191 catch err
0192 if strcmp(err.identifier, 'MATLAB:innerdim');
0193 error(['measurement pattern not compatible with number' ...
0194 'of electrodes for stimulation patter %d'],i);
0195 else
0196 rethrow(err);
0197 end
0198 end
0199
0200
0201 function v2meas = get_v2meas(n_elec,n_stim,stim)
0202 v2meas = sparse(n_elec*n_stim,0);
0203 for i=1:n_stim
0204 meas_pat= stim(i).meas_pattern;
0205 n_meas = size(meas_pat,1);
0206 v2meas((i-1)*n_elec + 1: i*n_elec,end+(1:n_meas)) = meas_pat.';
0207 end
0208
0209
0210 function [E, m_idx, pp] = mdl_reduction(E, mr, img, pp);
0211
0212 if isa(mr,'function_handle') || ischar(mr)
0213 mr = feval(mr,img.fwd_model);
0214 end
0215
0216 m_idx = mr.main_region;
0217 E = E(m_idx, m_idx);
0218 for i=1:length(mr.regions)
0219 invEi= mr.regions(i).invE;
0220
0221
0222
0223 field = mr.regions(i).field;
0224 field = find(img.fwd_model.coarse2fine(:,field));
0225 field = field(1);
0226 sigma = img.elem_data(field);
0227 E = E - sigma*invEi;
0228 end
0229
0230
0231 pp.QQ = pp.QQ(m_idx,:);
0232 pp.VV = pp.VV(m_idx,:);
0233 pp.N2E= pp.N2E(:,m_idx);
0234 pp.mr_mapper = cumsum(m_idx);
0235 pp.gnd_node = pp.mr_mapper(pp.gnd_node);
0236 if pp.gnd_node==0
0237 error('model_reduction removes ground node');
0238 end
0239
0240
0241 function unit_test_voltage_stims;
0242 stim = zeros(16,1); volt=stim; stim([1,4]) = NaN; volt([1,4]) = [1,2];
0243 stimulv.stim_pattern = stim;
0244 stimulv.volt_pattern = volt;
0245 stimulv.meas_pattern = [1,0,0,-1,zeros(1,12)];
0246
0247 img = mk_image( mk_common_model('a2c2',16),1);
0248 img.fwd_model.stimulation = stimulv;
0249 img.fwd_solve.get_all_nodes = 1;
0250 vh = fwd_solve_1st_order(img);
0251 unit_test_cmp('a2c2 Vstim #1', vh.meas, diff(volt(1:2,1)), 1e-14);
0252 unit_test_cmp('a2c2 Vstim #2', vh.volt(27+[1,4],1), volt([1,4]), 1e-14);
0253 tst = [ ...
0254 1.503131926779798; 1.412534629974291; 1.529078332819747;
0255 1.354399248512161; 1.546241676995996];
0256 unit_test_cmp('a2c2 Vstim #3', vh.volt(1:5:25,1), tst, 1e-14);
0257
0258 img.fwd_model.stimulation(2) = stimulv;
0259 img.fwd_model.stimulation(1).stim_pattern([1,4]) = 0;
0260 vh = fwd_solve_1st_order(img);
0261 unit_test_cmp('a2c2 Vstim #4', vh.volt(1:5:25,2), tst, 1e-14);
0262
0263 imgn = rmfield(img,'elem_data'); imgn.node_data = vh.volt;
0264 imgn.calc_colours.clim = 1; subplot(221); show_fem(imgn,1);
0265
0266 img = mk_image( mk_common_model('a2C2',16),1);
0267 img.fwd_model.stimulation = stimulv;
0268 img.fwd_solve.get_all_nodes = 1;
0269 vh = fwd_solve_1st_order(img);
0270 unit_test_cmp('a2C2 Vstim #1', vh.meas, diff(volt(1:2)), 1e-14);
0271 unit_test_cmp('a2C2 Vstim #2', vh.volt(num_nodes(img)+[1,4]), volt([1,4]), 1e-14);
0272 tst = [ ...
0273 1.499999999999998; 1.302478674263331; 1.609665333411830; ...
0274 1.215039511028270; 1.691145536046686];
0275 unit_test_cmp('a2C2 Vstim #3', vh.volt(1:5:25), tst, 1e-13);
0276
0277 imgn = rmfield(img,'elem_data'); imgn.node_data = vh.volt(1:num_nodes(img));
0278 imgn.calc_colours.clim = 1; subplot(222); show_fem(imgn,1);
0279
0280 stim = zeros(16,1); volt=stim; stim([1,4]) = NaN; stim(8)=1; volt([1,4]) = [1,1];
0281 img.fwd_model.stimulation(2).stim_pattern = stim;
0282 img.fwd_model.stimulation(2).volt_pattern = volt;
0283 img.fwd_model.stimulation(2).meas_pattern = [1,-1,zeros(1,14)];
0284 vh = fwd_solve_1st_order(img);
0285 unit_test_cmp('a2C2 Vstim #4', vh.volt(num_nodes(img)+[1,4],1), [1;2], 1e-14);
0286 unit_test_cmp('a2C2 Vstim #5', vh.volt(1:5:25,1), tst, 1e-13);
0287 unit_test_cmp('a2C2 Vstim #6', vh.volt(num_nodes(img)+[1,4],2), [1;1], 1e-14);
0288 tst = [ 1.029942389400905; 1.024198991581187; ...
0289 1.048244746016660; 1.006551737030278; 1.057453501332724];
0290 unit_test_cmp('a2C2 Vstim #7', vh.volt(1:5:25,2), tst, 1e-13);
0291
0292 imgn = rmfield(img,'elem_data'); imgn.node_data = vh.volt(1:num_nodes(img),2);
0293 subplot(223); show_fem(imgn,1);
0294
0295 stim = zeros(16,1); volt=stim; stim([3,6]) = NaN; volt([3,6]) = [1,2];
0296 img.fwd_model.stimulation(3).stim_pattern = stim;
0297 img.fwd_model.stimulation(3).volt_pattern = volt;
0298 img.fwd_model.stimulation(3).meas_pattern = [1,-1,zeros(1,14)];
0299 vh = fwd_solve_1st_order(img);
0300
0301 imgn = rmfield(img,'elem_data'); imgn.node_data = vh.volt(1:num_nodes(img),3);
0302 imgn.calc_colours.clim = 1; subplot(224); show_fem(imgn,1);
0303
0304 unit_test_cmp('a2C2 Vstim #7', vh.volt(num_nodes(img)+[3,6],3), [1;2], 1e-14);
0305
0306
0307
0308 function do_unit_test
0309 unit_test_voltage_stims;
0310
0311
0312 img = mk_image( mk_common_model('b2c2',16),1);
0313 vh = fwd_solve_1st_order(img);
0314 tst = [ ...
0315 0.959567140078593; 0.422175237237900; 0.252450963869202; ...
0316 0.180376116490602; 0.143799778367518];
0317 unit_test_cmp('b2c2 TEST', vh.meas(1:5), tst, 1e-12);
0318
0319 img.fwd_model = rmfield(img.fwd_model,'gnd_node');
0320 vh = fwd_solve_1st_order(img);
0321 unit_test_cmp('b2c2 gnd_node', vh.meas(1:5), tst, 1e-12);
0322
0323 img.fwd_solve.get_elec_curr = 1;
0324 vh = fwd_solve_1st_order(img);
0325 pp = fwd_model_parameters( img.fwd_model); EC = pp.N2E*pp.QQ;
0326 unit_test_cmp('b2b2 (CEM) elec_curr', vh.elec_curr, EC, 1e-11);
0327
0328 img.fwd_solve.get_all_meas = 1;
0329 vh = fwd_solve_1st_order(img);
0330 plot(vh.volt);
0331
0332 img = mk_image( mk_common_model('b2C2',16),1);
0333 vh = fwd_solve_1st_order(img);
0334 tst = [ 0.385629619754662; 0.235061644846908; 0.172837756982388
0335 0.142197580506776; 0.126808900182258; 0.120605655110661];
0336 unit_test_cmp('b2C2 (CEM) TEST', vh.meas(15:20), tst, 1e-12);
0337
0338 img.fwd_solve.get_elec_curr = 1;
0339 vh = fwd_solve_1st_order(img);
0340 pp = fwd_model_parameters( img.fwd_model); EC = pp.N2E*pp.QQ;
0341 unit_test_cmp('b2C2 (CEM) elec_curr', vh.elec_curr, EC, 1e-11);
0342
0343
0344 img.fwd_model.stimulation(1).stim_pattern(2) = 0;
0345 vh = fwd_solve_1st_order(img);
0346 last_msg = eidors_msg('last_msg');
0347 last_msg = last_msg(end-68:end);
0348 unit_test_cmp('gnd_node warning', last_msg, ...
0349 ' of current is flowing through ground node. Check stimulation pattern');
0350
0351
0352
0353 current = 4; measure=1;
0354 [R,img] = test_2d_resistor(current,measure);
0355 img.fwd_solve.get_all_nodes = 1;
0356 vs = fwd_solve_1st_order( img);
0357 va= measure*current*sum(R);
0358 unit_test_cmp('2D resistor test', va, vs.meas, 1e-12);
0359
0360 unit_test_cmp('2D R z_contact', ...
0361 [diff(vs.volt([13,1])), diff(vs.volt([14,12]))], ...
0362 R(2)/2*current*[1,-1], 1e-12);
0363 unit_test_cmp('2D R voltages', vs.volt(1:3:10)-vs.volt(1), ...
0364 R(1)*current*linspace(0,1,4)', 1e-12);
0365
0366 [R,img] = test_2d_resistor_faces(current,measure);
0367 vs = fwd_solve_1st_order( img);
0368 unit_test_cmp('2D resistor faces', va, vs.meas, 1e-12);
0369
0370
0371 [R,img] = test_3d_resistor(current,measure);
0372 img.fwd_solve.get_all_nodes = 1;
0373 vs = fwd_solve_1st_order( img);
0374 va= current*sum(R);
0375 unit_test_cmp('3D resistor test', va, vs.meas, 1e-10);
0376 unit_test_cmp('3D R voltages', vs.volt(1:12:72)-vs.volt(1), ...
0377 R(1)*current*linspace(0,1,6)', 1e-10);
0378 unit_test_cmp('3D R z_contact', ...
0379 [diff(vs.volt([73,1])), diff(vs.volt([74,72]))], ...
0380 R(2)/2*current*[1,-1], 1e-10);
0381
0382 [R,img] = test_3d_resistor_faces(current,measure);
0383 vs = fwd_solve_1st_order( img);
0384 unit_test_cmp('3D resistor faces', va, vs.meas, 1e-10);
0385
0386
0387 function [R,img] = test_2d_resistor(current,measure)
0388 conduc= .4 + 2*pi*j*10;
0389 z_contact= .1; wid = 3; len = 12;
0390
0391 fmdl=mk_grid_model([],linspace(0,wid,3), linspace(0,len,4));
0392 fmdl.electrode(1).nodes = find(fmdl.nodes(:,2) == 0);
0393 fmdl.electrode(2).nodes = find(fmdl.nodes(:,2) == len);
0394 [fmdl.electrode(:).z_contact] = deal(z_contact);
0395 fmdl.stimulation = stim_meas_list([1,2,1,2],2,current,measure);
0396 img= mk_image(fmdl,conduc);
0397
0398 Block_R = len / wid / conduc;
0399 Contact_R = z_contact/wid;
0400 R = [Block_R, 2*Contact_R];
0401
0402
0403 function [R,img] = test_2d_resistor_faces(current,measure)
0404 conduc= .4 + 2*pi*j*10;
0405 z_contact= .1; wid = 3; len = 12;
0406
0407 fmdl=mk_grid_model([],linspace(0,wid,3), linspace(0,len,4));
0408 bdy = fmdl.boundary;
0409 bdy( any(reshape(fmdl.nodes(bdy,2),size(bdy))>0,2),:)=[];
0410 fmdl.electrode(1).nodes = [];
0411 fmdl.electrode(1).faces = bdy;
0412 fmdl.electrode(2).nodes = find(fmdl.nodes(:,2) == len);
0413 [fmdl.electrode(:).z_contact] = deal(z_contact);
0414 fmdl.stimulation = stim_meas_list([1,2,1,2],2,current,measure);
0415 img= mk_image(fmdl,conduc);
0416
0417 Block_R = len / wid / conduc;
0418 Contact_R = z_contact/wid;
0419 R = [Block_R, 2*Contact_R];
0420
0421 function [R,img] = test_3d_resistor(current,measure);;
0422 conduc= .4 + 2*pi*j*10;
0423 z_contact= .1; wid = 2; len = 5; hig=3;
0424
0425 fmdl=mk_grid_model([],0:wid, 0:hig, 0:len);
0426 fmdl.electrode(1).nodes = find(fmdl.nodes(:,3) == 0);
0427 fmdl.electrode(2).nodes = find(fmdl.nodes(:,3) == len);
0428 [fmdl.electrode(:).z_contact] = deal(z_contact);
0429 fmdl.stimulation = stim_meas_list([1,2,1,2],2,current,measure);
0430 img= mk_image(fmdl,conduc);
0431
0432 Block_R = len / wid / hig / conduc;
0433 Contact_R = z_contact/(wid*hig);
0434 R = [Block_R, 2*Contact_R];
0435
0436
0437 function [R,img] = test_3d_resistor_faces(current,measure);;
0438 conduc= .4 + 2*pi*j*10;
0439 z_contact= .1; wid = 2; len = 5; hig=3;
0440
0441 fmdl=mk_grid_model([],0:wid, 0:hig, 0:len);
0442
0443 bdy = fmdl.boundary;
0444 bdy( any(reshape(fmdl.nodes(bdy,3),size(bdy))>0,2),:)=[];
0445 fmdl.electrode(1).nodes = [];
0446 fmdl.electrode(1).faces = bdy;
0447 fmdl.electrode(2).nodes = find(fmdl.nodes(:,3) == len);
0448 [fmdl.electrode(:).z_contact] = deal(z_contact);
0449 fmdl.stimulation = stim_meas_list([1,2,1,2],2,current,measure);
0450 img= mk_image(fmdl,conduc);
0451
0452 Block_R = len / wid / hig / conduc;
0453 Contact_R = z_contact/(wid*hig);
0454 R = [Block_R, 2*Contact_R];
0455
0456 function [R,img] = test_3d_resistor_old(current,measure);
0457 ll=5*1;
0458 ww=1*2;
0459 hh=1*3;
0460 conduc= .13;
0461 z_contact= 1e-1;
0462 scale = .46;
0463 nn=0;
0464 for z=0:ll; for x=0:ww; for y=0:hh
0465 nn=nn+1;
0466 mdl.nodes(nn,:) = [x,y,z];
0467 end; end; end
0468 mdl= eidors_obj('fwd_model','3D rectangle');
0469 mdl= mk_grid_model([],0:ww,0:hh,0:ll);
0470 mdl.nodes= mdl.nodes*scale;
0471 mdl= rmfield(mdl,'coarse2fine');
0472
0473 mdl.boundary= find_boundary(mdl.elems);
0474 mdl.gnd_node = 1;
0475 elec_nodes= [1:(ww+1)*(hh+1)];
0476 elec(1).nodes= elec_nodes; elec(1).z_contact= z_contact;
0477 elec(2).nodes= nn-elec_nodes+1; elec(2).z_contact= z_contact;
0478 stim.stim_pattern= [-1;1]*current;
0479 stim.meas_pattern= [-1,1]*measure;
0480 mdl.stimulation= stim;
0481 mdl.electrode= elec;
0482 mdl = mdl_normalize(mdl,0);
0483
0484 mdl.solve = @fwd_solve_1st_order;
0485 mdl.system_mat = @system_mat_1st_order;
0486 img= eidors_obj('image','3D rectangle', ...
0487 'elem_data', ones(size(mdl.elems,1),1) * conduc, ...
0488 'fwd_model', mdl);
0489
0490
0491 Block_R = ll / ww / hh / scale/ conduc;
0492 Contact_R = z_contact/(ww*hh)/scale^2;
0493
0494
0495
0496
0497
0498 R =[ Block_R , 2*Contact_R];