Related
I have data as real and imaginary parts of complex number, and I want to fit them according to a complex function. More in detail, they are data from electrochemical impedance spectroscopy (EIS) experiments, and the function comes from an equivalent circuit.
I am using Octave 7.2.0 in a Windows 10 computer. I need to use the leasqr algorithm, present in the Optim package. The leasqr used the Levenberg-Marquardt nonlinear regression, typical in EIS data fitting.
Regarding the data, xdata are linear frequency, y data are ReZ + j*ImZ.
If I try to fit the complex data with the complex fitting function, I get the following error:
error: weighted residuals are not real
error: called from
__lm_svd__ at line 147 column 20
leasqr at line 662 column 25
Code_for_StackOverflow at line 47 column 73
I tried to fit the real part of the data with the real part of the fitting function, and the imaginary parts of the data, with the imaginary part of the function. The fits are successfully performed, but I have two sets of fitted parameters, while I need only one set.
Here the code I wrote.
clear -a;
clf;
clc;
pkg load optim;
pkg load symbolic;
Linear_freq = [1051.432, 394.2871, 112.6535, 39.42871, 11.59668, 3.458659, 1.065641, 0.3258571, 0.1000221];
ReZ = [84.10412, 102.0962, 178.8031, 283.0663, 366.7088, 431.3653, 514.4105, 650.5853, 895.9588];
MinusImZ = [27.84804, 59.56786, 116.5972, 123.2293, 102.6806, 117.4836, 178.1147, 306.256, 551.2337];
Z = [88.5946744, 118.2030626, 213.4606653, 308.7264008, 380.8131426, 447.0776424, 544.3739605, 719.0646495, 1051.950932];
MinusPhase = [18.32042302, 30.26135402, 33.1083029, 23.52528583, 15.64255593, 15.23515301, 19.09841797, 25.2082044, 31.60167787];
ImZ = -MinusImZ;
Angular_freq = 2*pi*Linear_freq;
xdata = Angular_freq;
ydata = ReZ + j*ImZ;
Fitting_Function = #(xdata, p) (p(1) + ((p(2) + (1./(p(3)*(j*xdata).^0.5))).^-1 + (1./(p(4)*(j*xdata).^p(5))).^-1).^-1);
p = [80, 300, 6.63E-3, 5E-5, 0.8]; # True parameters values, taken with a dedicated software: 76, 283, 1.63E-3, 1.5E-5, 0.876
options.fract_prec = [0.0005, 0.0005, 0.0005, 0.0005, 0.0005].';
niter=400;
tol=1E-12;
dFdp="dfdp";
dp=1E-9*ones(size(p));
wt = abs(sqrt(ydata).^-1);
#[Fitted_Parameters_ReZ pfit_real cvg_real iter_real corp_real covp_real covr_real stdresid_real z_real r2_real] = leasqr(xdata, ReZ, p, Fitting_Function_ReZ, tol, niter, wt, dp, dFdp, options);
#[Fitted_Parameters_ImZ pfit_imag cvg_imag iter_imag corp_imag covp_imag covr_imag stdresid_imag z_imag r2_imag] = leasqr(xdata, ImZ, p, Fitting_Function_ImZ, tol, niter, wt, dp, dFdp, options);
[Fitted_Parameters pfit cvg iter corp covp covr stdresid z r2] = leasqr(xdata, ydata, p, Fitting_Function, tol, niter, wt, dp, dFdp, options);
#########################################################################
# Calculate the fitted functions, with the fitted paramteres array
#########################################################################
Fitted_Function_Real = real(pfit_real(1) + ((pfit_real(2) + (1./(pfit_real(3)*(j*xdata).^0.5))).^-1 + (1./(pfit_real(4)*(j*xdata).^pfit_real(5))).^-1).^-1);
Fitted_Function_Imag = imag(pfit_imag(1) + ((pfit_imag(2) + (1./(pfit_imag(3)*(j*xdata).^0.5))).^-1 + (1./(pfit_imag(4)*(j*xdata).^pfit_imag(5))).^-1).^-1);
Fitted_Function = Fitted_Function_Real + j.*Fitted_Function_Imag;
Fitted_Function_Mod = abs(Fitted_Function);
Fitted_Function_Phase = (-(angle(Fitted_Function))*(180./pi));
################################################################################
# Calculate the residuals, from https://iopscience.iop.org/article/10.1149/1.2044210
# An optimum fit is obtained when the residuals are spread randomly around the log Omega axis.
# When the residuals show a systematic deviation from the horizontal axis, e.g., by forming
# a "trace" around, above, or below the log co axis, the complex nonlinear least squares (CNLS) fit is not adequate.
################################################################################
Residuals_Real = (ReZ-Fitted_Function_Real)./Fitted_Function_Mod;
Residuals_Imag = (ImZ-Fitted_Function_Imag)./Fitted_Function_Mod;
################################################################################
# Calculate the chi-squared - reduced value, with the fitted paramteres array NOVA manual page 452
################################################################################
chi_squared_ReZ = sum(((ReZ-Fitted_Function_Real).^2)./Z.^2)
chi_squared_ImZ = sum(((ImZ-Fitted_Function_Imag).^2)./Z.^2)
Pseudo_chi_squared = sum((((ReZ-Fitted_Function_Real).^2)+((ImZ-Fitted_Function_Imag).^2))./Z.^2)
disp('The values of the parameters after the fit of the real function are '), disp(pfit_real);
disp('The values of the parameters after the fit of the imaginary function are '), disp(pfit_imag);
disp("R^2, the coefficient of multiple determination, intercept form (not suitable for non-real residuals) is "), disp(r2_real), disp(r2_imag);
###################################################
## PLOT Data and the Function
###################################################
#Set plot parameters
set(0, "defaultlinelinewidth", 1);
set(0, "defaulttextfontname", "Verdana");
set(0, "defaulttextfontsize", 20);
set(0, "DefaultAxesFontName", "Verdana");
set(0, 'DefaultAxesFontSize', 12);
figure(1);
## Nyquist plot (Argand diagram)
subplot(1,2,1, "align");
plot((ReZ), (MinusImZ), "o", "markersize", 2, (Fitted_Function_Real), -(Fitted_Function_Imag), "-k");
axis ("square");
grid on;
daspect([1 1 2]);
title ('Nyquist Plot - Argand Diagram');
xlabel ('Z'' / \Omega' , 'interpreter', 'tex');
ylabel ('-Z'''' / \Omega', 'interpreter', 'tex');
## Bode Modulus
subplot (2, 2, 2);
loglog((Linear_freq), (Z), "o", "markersize", 2, (Linear_freq), (Fitted_Function_Mod), "-k");
grid on;
title ('Bode Plot - Modulus');
xlabel ('\nu (Hz)' , 'interpreter', 'tex');
ylabel ('|Z| / \Omega', 'interpreter', 'tex');
## Bode Phase
subplot (2, 2, 4);
semilogx((Linear_freq), (MinusPhase), "o", "markersize", 2, (Linear_freq), (Fitted_Function_Phase), "-k");
set(gca,'YTick',0:10:90);
grid on;
title ('Bode Plot - Phase');
xlabel ('\nu (Hz)' , 'interpreter', 'tex');
ylabel ('-\theta (°)', 'interpreter', 'tex');
figure(2)
## Bode Z'
subplot (2, 1, 1);
semilogx((Linear_freq), (ReZ), "o", "markersize", 2, (Linear_freq), (Fitted_Function_Real), "-k");
grid on;
title ('Bode Plot Z''');
xlabel ('\nu (Hz)' , 'interpreter', 'tex');
ylabel ('Z'' / \Omega', 'interpreter', 'tex');
## Bode -Z''
subplot (2, 1, 2);
#subplot (2, 2, 4);
semilogx((Linear_freq), (MinusImZ), "o", "markersize", 2, (Linear_freq), -(Fitted_Function_Imag), "-k");
grid on;
title ('Bode Plot -Z''''');
xlabel ('\nu (Hz)' , 'interpreter', 'tex');
ylabel ('-Z'''' / \Omega', 'interpreter', 'tex');
figure(3)
## Residuals Real
subplot (2, 1, 1);
semilogx((Angular_freq), (Residuals_Real), "-o", "markersize", 2);
grid on;
title ('Residuals Real');
xlabel ('\omega (Hz)' , 'interpreter', 'tex');
ylabel ('\Delta_{re} / \Omega', 'interpreter', 'tex');
## Residuals Imaginary
subplot (2, 1, 2);
#subplot (2, 2, 4);
semilogx((Angular_freq), (Residuals_Imag), "-o", "markersize", 2);
grid on;
title ('Residuals Imaginary');
xlabel ('\omega (Hz)' , 'interpreter', 'tex');
ylabel ('\Delta_{im} / \Omega', 'interpreter', 'tex');
Octave should be able to handle complex numbers. What do I do wrong?
I was thinking to fit the real part of the data with the real part of the fitting function, and then using the Kramers-Kronig relations to get the imaginary part of the fitted function, but I would like to avoid this method, if possible.
Any help would be greatly appreciated, thanks in advance.
From your data drawing the complex impedances diagram makes appear a rather common shape that can be model with a lot of equivalent circuits :
Reference : https://fr.scribd.com/doc/71923015/The-Phasance-Concept
You chose the model n°2 probably according to some physical considerations. This is not the subject to be discussed here.
Also according to physical consideration and/or by graphical inspection you correctly assumed that one phasance is of Warbourg kind (Phi=-pi/4 ; nu=-1/2).
The problem is to fit an equation with five adjustable parameters. This is a difficult problem of non linear regression of a complex equation. The usual method consists in an iterative process starting from "guessed values" of the five parameters.
The "guessed values" have to be not far from the unknown correct values. One can find some approximates from graphical inspection of the impedances diagram. Often this is a cause of failure of convergence of the iterative process.
A more reliable method consists in using a combination of linear regression wrt most of the parameters and non-linear regression wrt only few parameters.
In the present case it is shown below that the nonlinear regression can be reduced to only one parameter while the other parameters can be handled by a simple linear regression. This is a big simplification.
A software for mixed linear and nonlinear regression (in cases involving several phasors) was developed in years 1980-1990. Infortunately I have no access to it presently.
Nevertheless in the present case of one phasor only we don't need a sledgehammer to crack a nut. The Newton-Raphson method is sufficient. Graphical inspection gives a rough approximate of (nu) between -0.7 and -0.8 The chosen initial value is nu=-0.75 giving the next first run :
Since all calculus are carried out in complex numbers the resulting values are complex instead of real as expected. They are noted ZR1, ZR2, ZP1, ZP2 to distinguish from real R1, R2, P1, P2. This is because the value of (nu) isn't optimal.
The more (nu) converges to the final value the more the imaginary parts vanishes. After a few runs of the Newton-Raphson process the imaginary parts become quite negligible. The final result is shown below.
Publications :
"Contribution à l'interprétation de certaines mesures d'impédances". 2-ième Forum sur les Imédances Electrochimiques, 28-29 octobre 1987.
"Calcul de réseau électriques équivalents à partir de mesures d'impédances". 3-ième Forum sur les Imédances Electrochimiques, 24 novembre 1988.
"Synthèse de circuits électiques équivalents à partir de mesures d'impédances complexes". 5-ième Forum sur les Imédances Electrochimiques, 28 novembre 1991.
I am trying to apply a bias-correction function on some gridded climate data (observed and modelled projections).
The qmap package has functions to perform bias correction on climate data,
I am trying to run the following
library(raster)
library(qmap)
#Create a rasterStack with observed and modelled data
r <- raster(ncol=20, nrow=20)
obs <- stack(lapply(1:100, function(x) setValues(r, runif(ncell(r))))) #observed data
mod <- stack(lapply(1:100, function(x) setValues(r, runif(ncell(r)))))*2 #modelled data (i want this unbiased)
#bias-correction function
f <- function(obs, mod, ...) {
obs <- t(obs)
qm.fit <- fitQmap(obs, t(mod), method="QUANT",qstep=0.01)
t(doQmap(mod, qm.fit, type="linear") )
}
x <- overlay(obs, mod, fun=f)
I got the following error
Error in (function (x, fun, filename = "", recycle = TRUE, forcefun = FALSE, :
cannot use this formula, probably because it is not vectorized
Can anybody help?
Thanks a million
With some light matrix reshaping, you can use the functions on matrices instead of rasters, which should work:
library(qmap)
library(raster)
r <- raster(ncol=20, nrow=20)
obs <- stack(lapply(1:100, function(x) setValues(r, runif(ncell(r))))) #observed data
mod <- stack(lapply(1:100, function(x) setValues(r, runif(ncell(r)))))*2 #modelled data (i want this unbiased)
qm.fit <- fitQmap(t(obs[]), t(mod[]), method="QUANT",qstep=0.01)
bias_corrected <- doQmap(t(mod[]), qm.fit, type="linear")
bias_corrected_arr <- as.array(t(bias_corrected))
#reshape array
dim(bias_corrected_arr) <- c(20,20,100)
# convert to rasterbrick
mod_Bcorrected <-setValues(brick(mod,values=FALSE),bias_corrected_arr)
# check output
> mod_Bcorrected
# class : RasterBrick
# dimensions : 20, 20, 400, 100 (nrow, ncol, ncell, nlayers)
# resolution : 18, 9 (x, y)
# extent : -180, 180, -90, 90 (xmin, xmax, ymin, ymax)
# coord. ref. : +proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0
# data source : in memory
# names : layer.1, layer.2, layer.3, layer.4, layer.5, layer.6, layer.7, layer.8, layer.9, layer.10, layer.11, layer.12, layer.13, layer.14, layer.15, ...
# min values : 2.136529e-03, 1.316528e-02, 4.380183e-03, 4.305932e-03, 5.293415e-03, 5.505609e-04, 9.494019e-05, 8.768495e-05, 8.171266e-04, 6.158992e-04, 3.248452e-03, 8.445294e-04, 2.856641e-03, 1.135400e-03, 3.255120e-03, ...
# max values : 0.9981889, 0.9995128, 0.9931242, 0.9975992, 0.9998942, 0.9986233, 0.9989456, 0.9952701, 0.9989686, 0.9958882, 0.9968628, 0.9975774, 0.9984780, 0.9910168, 0.9983078, ...
I am trying to learn few basic functions in Igraph- But, I am having problems computing the degrees from a gragph: see example below (I copied the following example from this site):
Example of data set:
edges <- matrix(c(103, 86, 24, 103, 103, 2, 92, 103, 87, 103, 103, 101, 103, 44), ncol=2, byrow=T)
Create graph
g <- graph(as.vector(t(edges)))
I can compute the degrees from the matrix edges:
degree(edges)
[1] 378 254 210 390 380 408 294 1230 1084
But I cannot compute the degrees from the graph g:
degree(g)
I am getting the following error:
Error in FUN(X[[1L]], ...) :
as.edgelist.sna input must be an adjacency matrix/array, edgelist matrix, network, or sparse matrix, or list thereof.
Anyone knows why I am getting this error?
So what happened here is igraph::degree is masked by sna::degree.
Just use:
igraph::degree
and it should work
I ran into same issue.
This worked for me:
net <- make_ring(10)
deg <- centralization.degree(net)$res
Is there an equivalent function of find(A>9,1) from matlab for numpy/scipy. I know that there is the nonzero function in numpy but what I need is the first index so that I can use the first index in another extracted column.
Ex: A = [ 1 2 3 9 6 4 3 10 ]
find(A>9,1) would return index 4 in matlab
The equivalent of find in numpy is nonzero, but it does not support a second parameter.
But you can do something like this to get the behavior you are looking for.
B = nonzero(A >= 9)[0]
But if all you are looking for is finding the first element that satisfies a condition, you are better off using max.
For example, in matlab, find(A >= 9, 1) would be the same as [~, idx] = max(A >= 9). The equivalent function in numpy would be the following.
idx = (A >= 9).argmax()
matlab's find(X, K) is roughly equivalent to numpy.nonzero(X)[0][:K] in python. #Pavan's argmax method is probably a good option if K == 1, but unless you know apriori that there will be a value in A >= 9, you will probably need to do something like:
idx = (A >= 9).argmax()
if (idx == 0) and (A[0] < 9):
# No value in A is >= 9
...
I'm sure these are all great answers but I wasn't able to make use of them. However, I found another thread that partially answers this:
MATLAB-style find() function in Python
John posted the following code that accounts for the first argument of find, in your case A>9 ---find(A>9,1)-- but not the second argument.
I altered John's code which I believe accounts for the second argument ",1"
def indices(a, func):
return [i for (i, val) in enumerate(a) if func(val)]
a = [1,2,3,9,6,4,3,10]
threshold = indices(a, lambda y: y >= 9)[0]
This returns threshold=3. My understanding is that Python's index starts at 0... so it's the equivalent of matlab saying 4. You can change the value of the index being called by changing the number in the brackets ie [1], [2], etc instead of [0].
John's original code:
def indices(a, func):
return [i for (i, val) in enumerate(a) if func(val)]
a = [1, 2, 3, 1, 2, 3, 1, 2, 3]
inds = indices(a, lambda x: x > 2)
which returns >>> inds [2, 5, 8]
Consider using argwhere in Python to replace MATLAB's find function. For example,
import numpy as np
A = [1, 2, 3, 9, 6, 4, 3, 10]
np.argwhere(np.asarray(A)>=9)[0][0] # Return first index
returns 3.
import numpy
A = numpy.array([1, 2, 3, 9, 6, 4, 3, 10])
index = numpy.where(A >= 9)
You can do this by first convert the list to an ndarray, then using the function numpy.where() to get the desired index.
I am using Octave.
My problem is this: I want to fill the bubbles of my scatter plot, as well as place a legend. But I get errors when I try to use 'filled', and no legend comes up when I use legend(...).
Part of my code looks like this:
%ALL SAMPLES, PHI(Signal) # THETA(Sample)=0
figure(5)
plot( Angles(:,1)([18:27]), ALL([18:27]), 10, [1 0 1]); %Magenta
hold on
scatter(Angles(:,1)([68:76]), ALL([68:76]), 10, [0 0 0]); %Black
scatter(Angles(:,1)([86:95]), ALL([86:95]), 10, [1 0 0]); %Red
scatter(Angles(:,1)([119:127]), ALL([119:127]), 10, [0 1 0]); %Green
scatter(Angles(:,1)([133:141]), ALL([133:141]), 10, [0 0 1]); %Blue
hold off
xlabel('Signal PMT angle (Sample angle at 0)');
ylabel('Normalized (signal/monitor) intensity');
legend('Control', 'Control', '1+2','Virgin','Cycle #1', 'Location','NorthEast');
title('Plot of All Samples, "-int Intensity"')
I know it should beplot( Angles(:,1)([18:27]), ALL([18:27]), 10, [1 0 1], 'filled');, but I receive errors when I do that. Also, a legend never seems to show up.
Apparently there is a problem with using legend with scatter in Octave. Based on this post:
http://octave.1599824.n4.nabble.com/Legend-in-scatter-plot-td3568032.html
the trick is to use the plot function to make scatter plot. I wrote the following function for plotting a bunch of scatter plots on the same axis.
This function takes in a bunch of cell arrays of the same length. Each element of the cell array corresponds to a separate series. The function returns a cell array of the same length containing the handle associated with each plot. The arguments of the function are explained below:
x_vals: a cell array of arrays of doubles corresponding to x values.
y_vals: a cell array of arrays of doubles corresponding to y values.
sizes: a cell array of doubles representing the size of the markers.
colors: a cell array of double arrays of length 3, representing [R, G, B] color values of the markers.
styles: a cell array of strings representing the shape of the markers.
function [handles] = scatter_series_set(x_vals, y_vals, sizes, colors, styles)
N = length(x_vals);
if ( (~ ( N == length(y_vals))) || (~ ( N == length(sizes))) || ...
(~ ( N == length(colors))) || (~ ( N == length(styles))) )
error('scatter_series_set: all arguments must be cell arrays of the same length');
end
%plot the first series
handles = cell([N, 1]);
handles{1} = plot(x_vals{1}, y_vals{1});
set(handles{1}, 'linestyle', 'none');
set(handles{1}, 'marker', styles{1});
set(handles{1}, 'markersize', sizes{1});
set(handles{1}, 'color', colors{1});
%plot additional series if present
if N > 1
hold on;
for ind = 2:N
handles{ind} = plot(x_vals{ind}, y_vals{ind});
set(handles{ind}, 'linestyle', 'none');
set(handles{ind}, 'marker', styles{ind});
set(handles{ind}, 'markersize', sizes{ind});
set(handles{ind}, 'color', colors{ind});
end
hold off;
end
end
The following example demonstrates how to use this function.
x1 = 0:(2*pi/100):(2*pi);
x2 = 2*x1;
y1 = sin(x1);
y2 = cos(x1);
y3 = sin(x2);
y4 = cos(x2);
names = {'a', 'b', 'c', 'd'};
x_vals = {x1, x1, x1, x1};
y_vals = {y1, y2, y3, y4};
sizes = {10, 10, 10, 10};
colors = {[1, 0, 0], [0, 0, 1], [0, 0, 0], [0.7071, 0, 0.7071]};
styles = {'^', 's', 'x', '+'}
scatter_series_set(x_vals, y_vals, sizes, colors, styles);
legend(names, 'location', 'southeast');
The example code produces the following plot:
The following works for me:
n = 100;
x = randn(n, 1);
y = randn(n, 1);
S = rand(n, 1)*20;
hold on
scatter(x(1:50), y(1:50), S(1:50), "red", "filled")
scatter(x(51:100), y(51:100), S(51:100), "green", "filled")
hold off
print('-depsc', 'bubbleplot.eps');
However, I'm not able to add a legend, and I didn't find any bug report or indication of a missing functionality for this. So, as an alternative, I would suggest adding marker and text to your plot.