|
| 1 | +function [CSX] = AddDjordjevicSarkarMaterial(varargin) |
| 2 | +% |
| 3 | +% Add a wideband dielectric material to a CSX struct using a multi-term |
| 4 | +% Debye fit of the Djordjevic–Sarkar model. |
| 5 | +% |
| 6 | +% Calculates Debye parameters from a single (eps_r, tanD) measurement via |
| 7 | +% 'calcDjordjevicSarkarApprox' and adds the material to the CSX struct. |
| 8 | +% |
| 9 | +% Input Parameters: |
| 10 | +% CSX - CSX struct to which the material will be added |
| 11 | +% materialName - String name of the material |
| 12 | +% |
| 13 | +% Name-Value Parameters (passed to `calcDjordjevicSarkarApprox`): |
| 14 | +% 'fMeas' - Measurement frequency [Hz] |
| 15 | +% 'epsRMeas' - Relative permittivity ε_r at 'fMeas' |
| 16 | +% 'tandMeas' - Loss tangent tan(δ) at 'fMeas' |
| 17 | +% 'f2' - Upper corner frequency of the Djordjevic–Sarkar model [Hz] |
| 18 | +% 'f1' - Lower corner frequency [Hz] (if `lowFreqEvalType` = 0) |
| 19 | +% 'epsRdc' - Permittivity at DC (if `lowFreqEvalType` = 1) |
| 20 | +% 'sigmaDC' - Optional DC conductivity [S/m] |
| 21 | +% 'nTermsPerDec' - Number of Debye terms per frequency decade |
| 22 | +% |
| 23 | +% Output: |
| 24 | +% CSX - Updated CSX struct including the defined wideband dielectric material |
| 25 | +% |
| 26 | +% Note: |
| 27 | +% - Internally uses 'calcDjordjevicSarkarApprox' to generate model parameters. |
| 28 | +% - See 'calcDjordjevicSarkarApprox' for detailed description of the model |
| 29 | +% fitting. |
| 30 | +% |
| 31 | +% Example: |
| 32 | +% CSX = AddDjordjevicSarkarMaterial(CSX, 'MyMaterial', ... |
| 33 | +% 'fMeas', 1e9, 'epsRMeas', 4.2, 'tandMeas', 0.02, ... |
| 34 | +% 'f1', 1e6, 'f2', 200e9); |
| 35 | +% |
| 36 | +% See also: calcDjordjevicSarkarApprox, AddDebyeMaterial |
| 37 | +% |
| 38 | +% Version History: |
| 39 | +% v1.0 2025-06-30 Tobias Ammann Initial version |
| 40 | + |
| 41 | + p = inputParser(); |
| 42 | + p.FunctionName = 'AddDjordjevicSarkarMaterial'; |
| 43 | + |
| 44 | + p.addRequired('CSX', @isstruct); |
| 45 | + p.addRequired('matName', @ischar); |
| 46 | + |
| 47 | + % Required Parameters |
| 48 | + % Set as Parameters, checked manually too match openEMS name-value pair style |
| 49 | + p.addParameter('fMeas', [], @isPositiveScalar); % Hz |
| 50 | + p.addParameter('epsRMeas', [], @isPositiveScalar); |
| 51 | + p.addParameter('tandMeas', [], @isNonNegScalar); |
| 52 | + p.addParameter('f2', [], @isPositiveScalar); % Upper fit frequency, Hz |
| 53 | + |
| 54 | + % Optional Parameters |
| 55 | + p.addParameter('lowFreqEvalType', 0, @isIntegerScalar); % 0 = use f1, 1 = use epsRdc |
| 56 | + p.addParameter('f1', nan, @isPositiveScalar); % lowFreqEvalType = 0, Hz |
| 57 | + p.addParameter('epsRdc', inf, @isPositiveScalar); % lowFreqEvalType = 1 |
| 58 | + p.addParameter('sigmaDC', 0, @isNonNegScalar); % Siemens |
| 59 | + p.addParameter('nTermsPerDec', 1, @(x) isScalar(x) && x >= 1); % Number of Debye terms per decade |
| 60 | + p.addParameter('plotEn', 0, @isIntegerScalar); % Enable/Disable plots of the model |
| 61 | + |
| 62 | + % Parse and manually verify required parameters |
| 63 | + p.parse(varargin{:}); |
| 64 | + |
| 65 | + requiredParams = {'fMeas', 'epsRMeas', 'tandMeas', 'f2'}; |
| 66 | + for i = 1:numel(requiredParams) |
| 67 | + param = requiredParams{i}; |
| 68 | + if ismember(param, p.UsingDefaults) |
| 69 | + error('%s: Missing required parameter ''%s''.', p.FunctionName, param); |
| 70 | + end |
| 71 | + end |
| 72 | + |
| 73 | + if ((p.Results.lowFreqEvalType == 0) && ismember('f1', p.UsingDefaults)) |
| 74 | + error(['%s: For ''lowFreqEvalType=0'' a value for f1 (Djordjevic Sarkar ',... |
| 75 | + 'low corner frequency)must be specified.'], p.FunctionName); |
| 76 | + end |
| 77 | + |
| 78 | + if ((p.Results.lowFreqEvalType == 1) && ismember('epsRdc', p.UsingDefaults)) |
| 79 | + error(['%s: For ''lowFreqEvalType=1'', a value for epsRdc (value of ',... |
| 80 | + 'EpsilonR at DC) must be specified.'], p.FunctionName); |
| 81 | + end |
| 82 | + |
| 83 | + % Fit the model and receive Debye model parametes for openEMS |
| 84 | + paramDebye = CalcDjordjevicSarkarApprox(... |
| 85 | + 'fMeas', p.Results.fMeas,... |
| 86 | + 'epsRMeas', p.Results.epsRMeas,... |
| 87 | + 'tandMeas', p.Results.tandMeas,... |
| 88 | + 'f1', p.Results.f1,... |
| 89 | + 'f2', p.Results.f2,... |
| 90 | + 'lowFreqEvalType', p.Results.lowFreqEvalType,... |
| 91 | + 'epsRdc', p.Results.epsRdc,... |
| 92 | + 'sigmaDC', p.Results.sigmaDC,... |
| 93 | + 'nTermsPerDec', p.Results.nTermsPerDec,... |
| 94 | + 'plotEn', p.Results.plotEn); |
| 95 | + |
| 96 | + CSX = p.Results.CSX; |
| 97 | + matName = p.Results.matName; |
| 98 | + |
| 99 | + CSX = AddDebyeMaterial(CSX, matName); |
| 100 | + CSX = SetMaterialProperty(CSX, matName, ... |
| 101 | + 'Epsilon', paramDebye.epsInf, ... % Epsilon here is acutally EpsilonRinf |
| 102 | + 'Kappa', paramDebye.sigmaDC); |
| 103 | + |
| 104 | + for i = 1:length(paramDebye.wi) |
| 105 | + |
| 106 | + CSX = SetMaterialProperty(CSX, matName,... |
| 107 | + ['EpsilonDelta_', int2str(i)], paramDebye.deltaEpsT(i),... |
| 108 | + ['EpsilonRelaxTime_', int2str(i)], 1/paramDebye.wi(i)); |
| 109 | + |
| 110 | + end |
| 111 | +end |
| 112 | + |
| 113 | +% Validation functions for input argument checks |
| 114 | +function val = isNonNegScalar(x) |
| 115 | + val = isnumeric(x) && isscalar(x) && (x >= 0); |
| 116 | +end |
| 117 | + |
| 118 | +function val = isPositiveScalar(x) |
| 119 | + val = isnumeric(x) && isscalar(x) && (x > 0); |
| 120 | +end |
| 121 | + |
| 122 | +function val = isIntegerScalar(x) |
| 123 | + val = isnumeric(x) && isscalar(x) && (round(x) == x); |
| 124 | +end |
| 125 | + |
| 126 | +function val = isScalar(x) |
| 127 | + val = isnumeric(x) && isscalar(x); |
| 128 | +end |
0 commit comments