%
% Tutorials / Dipole SAR + Power budget
%
% Description at:
% http://openems.de/index.php/Tutorial:_Dipole_SAR
%
% Tested with
%  - openEMS v0.0.36
%
% (C) 2013-2025 Thorsten Liebig <thorsten.liebig@gmx.de>

close all
clear
clc

%% switches & options...
postprocessing_only = 0;

%% prepare simulation folder
Sim_Path = 'tmp_Dipole_SAR';
Sim_CSX = 'Dipole_SAR.xml';

%% setup the simulation
physical_constants;
unit = 1e-3; % all lengths in mm

feed.R = 50; % feed resistance

%% define phantom
phantom{1}.name='skin';
phantom{1}.epsR = 50;
phantom{1}.kappa = 0.65; % S/m
phantom{1}.density = 1100; % kg/m^3
phantom{1}.radius = [80 100 100]; % ellipsoide
phantom{1}.center = [100 0 0];

phantom{2}.name='headbone';
phantom{2}.epsR = 13;
phantom{2}.kappa = 0.1; % S/m
phantom{2}.density = 2000; % kg/m^3
phantom{2}.radius = [75 95 95]; % ellipsoide
phantom{2}.center = [100 0 0];

phantom{3}.name='brain';
phantom{3}.epsR = 60;
phantom{3}.kappa = 0.7; % S/m
phantom{3}.density = 1040; % kg/m^3
phantom{3}.radius = [65 85 85]; % ellipsoide
phantom{3}.center = [100 0 0];

%% setup FDTD parameter & excitation function
f0 = 1e9; % center frequency
lambda0 = c0/f0;

f_stop = 1.5e9; % 20 dB corner frequency
lambda_min = c0/f_stop;

mesh_res_air = lambda_min/20/unit;
mesh_res_phantom = 2.5;

dipole_length = 0.48*lambda0/unit;
disp(['Lambda-half dipole length: ' num2str(dipole_length) 'mm'])

%%
FDTD = InitFDTD('CellConstantMaterial', 1); % make sure the material is constant per voxel
FDTD = SetGaussExcite( FDTD, 0, f_stop );
% apply PML-8 boundary conditions in all directions
BC = {'PML_8' 'PML_8' 'PML_8' 'PML_8' 'PML_8' 'PML_8'};
FDTD = SetBoundaryCond( FDTD, BC );

%% setup CSXCAD geometry & mesh
CSX = InitCSX();

%% Dipole
CSX = AddMetal( CSX, 'Dipole' ); % create a perfect electric conductor (PEC)
CSX = AddBox(CSX, 'Dipole', 1, [0 0 -dipole_length/2], [0 0 dipole_length/2]);

% mesh lines for the dipole
mesh.x = 0;
mesh.y = 0;
mesh.z = [-dipole_length/2-[-1/3 2/3]*mesh_res_phantom dipole_length/2+[-1/3 2/3]*mesh_res_phantom];

%% add the dielectrics
for n=1:numel(phantom)
  CSX = AddMaterial( CSX, phantom{n}.name );
  CSX = SetMaterialProperty( CSX, phantom{n}.name, 'Epsilon', phantom{n}.epsR, 'Kappa', phantom{n}.kappa, 'Density', phantom{n}.density);
  CSX = AddSphere( CSX, phantom{n}.name, 10+n, [0 0 0], 1,'Transform',{'Scale',phantom{n}.radius, 'Translate', phantom{n}.center} );

  %% mesh lines for the dielectrics
  mesh.x = [mesh.x phantom{n}.radius(1)*[-1 1]+phantom{n}.center(1) ];
  mesh.y = [mesh.y phantom{n}.radius(2)*[-1 1]+phantom{n}.center(2) ];
  mesh.z = [mesh.z phantom{n}.radius(3)*[-1 1]+phantom{n}.center(3) ];
end

%% apply the excitation & resist as a current source
[CSX port] = AddLumpedPort(CSX, 100, 1, feed.R, [-0.1 -0.1 -mesh_res_phantom/2], [0.1 0.1 +mesh_res_phantom/2], [0 0 1], true);

% mesh lines for the port
mesh.z = [mesh.z -mesh_res_phantom/2 +mesh_res_phantom/2];

%% smooth the mesh over the dipole and phantom
mesh = SmoothMesh(mesh, mesh_res_phantom);

%% add lines for the air-box
mesh.x = [mesh.x -200 250+100];
mesh.y = [mesh.y -250 250];
mesh.z = [mesh.z -250 250];

% smooth the final mesh (incl. air box)
mesh = SmoothMesh(mesh, mesh_res_air, 1.2);

%% dump SAR
start = [-30 -120 -120];
stop = [200  120  120];
CSX = AddDump( CSX, 'SAR', 'DumpType', 29, 'Frequency', f0,'FileType',1,'DumpMode',2);
CSX = AddBox( CSX, 'SAR', 0, start, stop);

%% nf2ff calc
start = [mesh.x(1)   mesh.y(1)   mesh.z(1)];
stop  = [mesh.x(end) mesh.y(end) mesh.z(end)];
[CSX nf2ff] = CreateNF2FFBox(CSX, 'nf2ff', start, stop, 'OptResolution', lambda_min/15/unit);

%%
% add 10 equidistant cells (air)
% around the structure to keep the pml away from the nf2ff box
mesh = AddPML( mesh, 10 );

% Define the mesh
CSX = DefineRectGrid(CSX, unit, mesh);

%%
if (postprocessing_only==0)
    CleanupSimPath(Sim_Path);

    % write openEMS compatible xml-file
    WriteOpenEMS( [Sim_Path '/' Sim_CSX], FDTD, CSX );

    % show the structure
    CSXGeomPlot( [Sim_Path '/' Sim_CSX] );

    % run openEMS
    RunOpenEMS( Sim_Path, Sim_CSX );
end


%% postprocessing & make the plots
freq = linspace(500e6, 1500e6, 501 );
port = calcPort(port, Sim_Path, freq);

s11 = port.uf.ref./port.uf.inc;
Zin = port.uf.tot./port.if.tot;

Pin_f0 = interp1(freq, port.P_acc, f0);

%%
% plot feed point impedance
figure
plot( freq/1e6, real(Zin), 'k-', 'Linewidth', 2 );
hold on
grid on
plot( freq/1e6, imag(Zin), 'r--', 'Linewidth', 2 );
title( 'feed point impedance' );
xlabel( 'frequency f / MHz' );
ylabel( 'impedance Z_{in} / Ohm' );
legend( 'real', 'imag' );

% plot reflection coefficient S11
figure
plot( freq/1e9, 20*log10(abs(s11)), 'k-', 'Linewidth', 2 );
grid on
title( 'reflection coefficient' );
xlabel( 'frequency f / MHz' );
ylabel( 'S_{11} (dB)' );

SAR_fn = [Sim_Path '/SAR_1g.h5'] % calculated SAR output
CalcSAR([Sim_Path '/SAR.h5'], SAR_fn, 'mass', 1, 'method', 'IEEE_62704')

%% read SAR and visualize
SAR_field = ReadHDF5Dump(SAR_fn);

SAR = SAR_field.FD.values{1};
ptotal = ReadHDF5Attribute(SAR_fn,'/FieldData/FD/f0','power');
mass = ReadHDF5Attribute(SAR_fn,'/','mass');

%% calculate 3D pattern
phi = 0:3:360;
theta = 0:3:180;

disp( 'calculating 3D far field pattern...' );
nf2ff = CalcNF2FF(nf2ff, Sim_Path, f0, theta*pi/180, phi*pi/180, 'Outfile','3D_Pattern.h5');

%%
disp(['max SAR: ' num2str(max(SAR(:))/Pin_f0) ' W/kg normalized to 1 W accepted power']);
disp(['whole body SAR: ' num2str(ptotal/Pin_f0/mass) ' W/kg normalized to 1 W accepted power']);
disp(['accepted power: ' num2str(Pin_f0) ' W (100 %)']);
disp(['radiated power: ' num2str(nf2ff.Prad) ' W ( ' num2str(round(100*(nf2ff.Prad) / Pin_f0)) ' %)']);
disp(['absorbed power: ' num2str(ptotal) ' W ( ' num2str(round(100*(ptotal) / Pin_f0)) ' %)']);
disp(['power budget:   ' num2str(100*(nf2ff.Prad + ptotal) / Pin_f0) ' %']);

%%  plot on a x/y-plane
[SAR_field SAR_mesh] = ReadHDF5Dump(SAR_fn,'Range',{[],[],0});
figure
[X Y] = ndgrid(SAR_mesh.lines{1},SAR_mesh.lines{2});
h = pcolor(X,Y,log10(SAR_field.FD.values{1}/abs(Pin_f0)));
title( 'logarithmic SAR on an xy-plane' );
xlabel('x -->')
ylabel('y -->')
axis equal tight
set(h,'EdgeColor','none');

%%  plot on a x/z-plane
[SAR_field SAR_mesh] = ReadHDF5Dump(SAR_fn,'Range',{[],0,[]});
figure
[X Z] = ndgrid(SAR_mesh.lines{1},SAR_mesh.lines{3});
h = pcolor(X,Z,log10(squeeze(SAR_field.FD.values{1}))/abs(Pin_f0));
title( 'logarithmic SAR on an xz-plane' );
xlabel('x -->')
ylabel('z -->')
axis equal tight
set(h,'EdgeColor','none');

%% dump SAR to vtk file
disp(['Full 1g normalized SAR has been dumped to vtk file! Use Paraview to visualize']);
ConvertHDF5_VTK(SAR_fn,[Sim_Path '/SAR'],'weight',1/abs(Pin_f0),'FieldName','SAR_1g' );

