4 dimensional choice array in JAGS - jags

I am trying to run a model in JAGS where I have the following dimensions:
n Subjects (nSubjects)
2 sessions (nSessions)
4 conditions (nConditions)
42 trials per condition (nTrials)
It is a reinforcement learning model weighting action- and stimulus values.
I started providing jags with 4D (nSubjects x nSessions x nTrials x nConditions) variables. These were variables such as choice, reward magnitudes associated with available options, stimulus and action properties of choice, choice outcome.
JAGS then returned the following error:
'Error: 4D and higher dimensional arrays not accepted'
I then tried to solve the problem by providing four different variables for the four different conditions. This made the code much more complex and ugly because I resorted to nested ifelse loops (see code below). Does not seem like a proper solution, but might work.
However, I don't know how to handle the fact that my choice data is 4D as well. All other variables are on the right hand side of the equation, so I can work with above-mentioned nested ifelse loops. However, if I provide four different choice varibles, this would not work (as far as I can see, ifelse loops are functions, not a control flow statement, so I cannot state
ifelse(c == 1, yCond1 = ..., ifelse(c == 2, yCond2 = ..., ifelse(c == 3, yCond3 = ..., yCond4 = ...)))
I also tried to create a new variable within JAGS (where 4D variables can be created, they apparently just cannot be passed to JAGS), like so:
y[i,s,1:nTrials,c] = ifelse(c == 1,yAct1,ifelse(c==2,yAct2,ifelse(c==3,yStim1,yStim2)))
But then the problem is that I have NaN values in the choice data which are allowed in the dependent variable (left-hand side of equation), but not in predictor variables (right-hand side) where they now appear.
I assume there must be an elegant way to do this, I just can't come up with one.
Any help greatly appreciated, below my matlab and jags code. (model is still early stage, hierarchical priors etc. still to be implemented)
Cheers, David
muLogBetaL=-2.3;muLogBetaU=3.4; %bounds on mean of distribution log beta
sigmaLogBetaL=0.01;sigmaLogBetaU=sqrt(((muLogBetaU-muLogBetaL)^2)/12);%bounds on the std of distribution of log beta
dataStruct = struct(...
'nConds', 4,...
'nTrials', size(choiceComb_Cond1,3),...
'nSubjs', numel(subjIDs),...
'nSessions', 2,...
'choiceRewCond1', choiceRewarded_Cond1,...
'choiceRewCond2', choiceRewarded_Cond2,...
'choiceRewCond3', choiceRewarded_Cond3,...
'choiceRewCond4', choiceRewarded_Cond4,...
'choicePushCond1', choicePush_Cond1,...
'choicePushCond2', choicePush_Cond2,...
'choicePushCond3', choicePush_Cond3,...
'choicePushCond4', choicePush_Cond4,...
'choiceYellCond1', choiceYell_Cond1,...
'choiceYellCond2', choiceYell_Cond2,...
'choiceYellCond3', choiceYell_Cond3,...
'choiceYellCond4', choiceYell_Cond4,...
'winAmtPushCond1', winAmtPush_Cond1,...
'winAmtPushCond2', winAmtPush_Cond2,...
'winAmtPushCond3', winAmtPush_Cond3,...
'winAmtPushCond4', winAmtPush_Cond4,...
'winAmtYellCond1', winAmtYell_Cond1,...
'winAmtYellCond2', winAmtYell_Cond2,...
'winAmtYellCond3', winAmtYell_Cond3,...
'winAmtYellCond4', winAmtYell_Cond4,...
'yCond1', choiceComb_Cond1,...
'yCond2', choiceComb_Cond2,...
'yCond3', choiceComb_Cond3,...
'yCond4', choiceComb_Cond4);
monitorParameters = {'alpha','beta','acw','vPush'};
initStruct.alpha=ones(numel(subjIDs),2,4).*.5;
initStruct.beta=ones(numel(subjIDs),2).*.02;
initStruct.acw=ones(numel(subjIDs),2,4).*.02;
init0(1) = initStruct;% a structure array that has the initial values for all latent variables for each chain
doParallel = 0;
modelName = 'modelRL';
nChains = 1;
nBurnin = 500;
nSamples = 5000;
nThin = 1;
doDIC = 0;
fprintf( 'Running JAGS ...\n' ); % display
[samples, stats] = matjags( ...
dataStruct, ... % Observed data
fullfile(pwd, [modelName '.txt']), ... % File that contains model definition
init0 , ... % Initial values for latent variables
1 , ... % which copy of matjags to run
'doparallel' , doParallel, ... % Parallelization flag
'nchains', nChains,... % Number of MCMC chains
'nburnin', nBurnin,... % Number of burnin steps
'nsamples', nSamples, ... % Number of samples to extract
'thin', nThin, ... % Thinning parameter
'dic', doDIC, ... % Do the DIC?
'monitorparams', monitorParameters, ... % List of latent variables to monitor
'savejagsoutput' , 0 , ... % Save command line output produced by JAGS?
'verbosity' , 2 , ... % 0=do not produce any output; 1=minimal text output; 2=maximum text output
'cleanup' , 0 ,... % clean up of temporary files?
'rndseed',1); % Randomise seed; 0=no; 1=yes
model{
for(i in 1:nSubjs){
for(s in 1:nSessions){
for(c in 1:nConds){
# STARTING VALUES FOR FIRST TRIAL
# Reward probability estimate for pushing/yellow
pPushRew[i,s,1,c] = .5
pYellRew[i,s,1,c] = .5
# Expected values
vPush[i,s,1,c] = ifelse(c == 1,
pPushRew[i,s,1,c] * winAmtPushCond1[i,s,1],
ifelse(c == 2,
pPushRew[i,s,1,c] * winAmtPushCond2[i,s,1],
ifelse(c == 3,
pPushRew[i,s,1,c] * winAmtPushCond3[i,s,1],
pPushRew[i,s,1,c] * winAmtPushCond4[i,s,1])))
vYell[i,s,1,c] = ifelse(c == 1,
pYellRew[i,s,1,c] * winAmtYellCond1[i,s,1],
ifelse(c == 2,
pYellRew[i,s,1,c] * winAmtYellCond2[i,s,1],
ifelse(c == 3,
pYellRew[i,s,1,c] * winAmtYellCond3[i,s,1],
pYellRew[i,s,1,c] * winAmtYellCond4[i,s,1])))
vPull[i,s,1,c] = ifelse(c == 1,
(1-pPushRew[i,s,1,c]) * (100-winAmtPushCond1[i,s,1]),
ifelse(c == 2,
(1-pPushRew[i,s,1,c]) * (100-winAmtPushCond2[i,s,1]),
ifelse(c == 3,
(1-pPushRew[i,s,1,c]) * (100-winAmtPushCond3[i,s,1]),
(1-pPushRew[i,s,1,c]) * (100-winAmtPushCond4[i,s,1]))))
vBlue[i,s,1,c] = ifelse(c == 1,
(1-pYellRew[i,s,1,c]) * (100-winAmtYellCond1[i,s,1]),
ifelse(c == 2,
(1-pYellRew[i,s,1,c]) * (100-winAmtYellCond2[i,s,1]),
ifelse(c == 3,
(1-pYellRew[i,s,1,c]) * (100-winAmtYellCond3[i,s,1]),
(1-pYellRew[i,s,1,c]) * (100-winAmtYellCond4[i,s,1]))))
# Weighted Action-Color combination value
AC[i,s,1,c,1] = (acw[i,s,c] * vPull[i,s,1,c]) + ((1-acw[i,s,c]) * vBlue[i,s,1,c])
AC[i,s,1,c,2] = (acw[i,s,c] * vPull[i,s,1,c]) + ((1-acw[i,s,c]) * vYell[i,s,1,c])
AC[i,s,1,c,3] = (acw[i,s,c] * vPush[i,s,1,c]) + ((1-acw[i,s,c]) * vBlue[i,s,1,c])
AC[i,s,1,c,4] = (acw[i,s,c] * vPush[i,s,1,c]) + ((1-acw[i,s,c]) * vYell[i,s,1,c])
# choice probability for each action-color combination
pAC[i,s,1,c,1] = exp(beta[i,s]*AC[i,s,1,c,1])/(exp(beta[i,s]*AC[i,s,1,c,2])+exp(beta[i,s]*AC[i,s,1,c,3])+exp(beta[i,s]*AC[i,s,1,c,4]))
pAC[i,s,1,c,2] = exp(beta[i,s]*AC[i,s,1,c,2])/(exp(beta[i,s]*AC[i,s,1,c,1])+exp(beta[i,s]*AC[i,s,1,c,3])+exp(beta[i,s]*AC[i,s,1,c,4]))
pAC[i,s,1,c,3] = exp(beta[i,s]*AC[i,s,1,c,3])/(exp(beta[i,s]*AC[i,s,1,c,1])+exp(beta[i,s]*AC[i,s,1,c,2])+exp(beta[i,s]*AC[i,s,1,c,4]))
pAC[i,s,1,c,4] = exp(beta[i,s]*AC[i,s,1,c,4])/(exp(beta[i,s]*AC[i,s,1,c,1])+exp(beta[i,s]*AC[i,s,1,c,2])+exp(beta[i,s]*AC[i,s,1,c,3]))
# realizing choice
y[i,s,1,c] ~ dcat(pAC[i,s,1,c,1:4]) #THIS DOESN'T WORK BECAUSE 4D ARRAY
# LOOP OVER TRIALS
for(t in 2:nTrials){
#updating of reward probability estimate for pushing
pPushRew[i,s,t,c] = ifelse(c==1,
ifelse(choicePushCond1[i,s,t-1] == 1,
pPushRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond1[i,s,t-1]-pPushRew[i,s,t-1,c]),
pPushRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond1[i,s,t-1]-1))-pPushRew[i,s,t-1,c])),
ifelse(c==2,
ifelse(choicePushCond2[i,s,t-1] == 1,
pPushRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond2[i,s,t-1]-pPushRew[i,s,t-1,c]),
pPushRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond2[i,s,t-1]-1))-pPushRew[i,s,t-1,c])),
ifelse(c==3,
ifelse(choicePushCond3[i,s,t-1] == 1,
pPushRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond3[i,s,t-1]-pPushRew[i,s,t-1,c]),
pPushRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond3[i,s,t-1]-1))-pPushRew[i,s,t-1,c])),
ifelse(choicePushCond4[i,s,t-1] == 1,
pPushRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond4[i,s,t-1]-pPushRew[i,s,t-1,c]),
pPushRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond4[i,s,t-1]-1))-pPushRew[i,s,t-1,c])))))
#updating of reward probability estimate for yellow
pYellRew[i,s,t,c] = ifelse(c==1,
ifelse(choiceYellCond1[i,s,t-1] == 1,
pYellRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond1[i,s,t-1]-pYellRew[i,s,t-1,c]),
pYellRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond1[i,s,t-1]-1))-pYellRew[i,s,t-1,c])),
ifelse(c==2,
ifelse(choiceYellCond2[i,s,t-1] == 1,
pYellRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond2[i,s,t-1]-pYellRew[i,s,t-1,c]),
pYellRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond2[i,s,t-1]-1))-pYellRew[i,s,t-1,c])),
ifelse(c==3,
ifelse(choiceYellCond3[i,s,t-1] == 1,
pYellRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond3[i,s,t-1]-pYellRew[i,s,t-1,c]),
pYellRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond3[i,s,t-1]-1))-pYellRew[i,s,t-1,c])),
ifelse(choiceYellCond4[i,s,t-1] == 1,
pYellRew[i,s,t-1,c] + alpha[i,s,c]*(choiceRewCond4[i,s,t-1]-pYellRew[i,s,t-1,c]),
pYellRew[i,s,t-1,c] + alpha[i,s,c]*((-1*(choiceRewCond4[i,s,t-1]-1))-pYellRew[i,s,t-1,c])))))
# EV for pushing/yellow
vPush[i,s,t,c] = ifelse(c==1,
pPushRew[i,s,t,c] * winAmtPushCond1[i,s,t],
ifelse(c==2,
pPushRew[i,s,t,c] * winAmtPushCond2[i,s,t],
ifelse(c==3,
pPushRew[i,s,t,c] * winAmtPushCond3[i,s,t],
pPushRew[i,s,t,c] * winAmtPushCond4[i,s,t])))
vYell[i,s,t,c] = ifelse(c==1,
pYellRew[i,s,t,c] * winAmtYellCond1[i,s,t],
ifelse(c==2,
pYellRew[i,s,t,c] * winAmtYellCond2[i,s,t],
ifelse(c==3,
pYellRew[i,s,t,c] * winAmtYellCond3[i,s,t],
pYellRew[i,s,t,c] * winAmtYellCond4[i,s,t])))
# EV for pulling/blue
vPull[i,s,t,c] = ifelse(c==1,
(1-pPushRew[i,s,t,c]) * (100-winAmtPushCond1[i,s,t]),
ifelse(c==2,
(1-pPushRew[i,s,t,c]) * (100-winAmtPushCond2[i,s,t]),
ifelse(c==3,
(1-pPushRew[i,s,t,c]) * (100-winAmtPushCond3[i,s,t]),
(1-pPushRew[i,s,t,c]) * (100-winAmtPushCond4[i,s,t]))))
vBlue[i,s,t,c] = ifelse(c==1,
(1-pYellRew[i,s,t,c]) * (100-winAmtYellCond1[i,s,t]),
ifelse(c==2,
(1-pYellRew[i,s,t,c]) * (100-winAmtYellCond2[i,s,t]),
ifelse(c==3,
(1-pYellRew[i,s,t,c]) * (100-winAmtYellCond3[i,s,t]),
(1-pYellRew[i,s,t,c]) * (100-winAmtYellCond4[i,s,t]))))
# Weighted Action-Color combination value
AC[i,s,t,c,1] = (acw[i,s,c] * vPull[i,s,t,c]) + ((1-acw[i,s,c]) * vBlue[i,s,t,c])
AC[i,s,t,c,2] = (acw[i,s,c] * vPull[i,s,t,c]) + ((1-acw[i,s,c]) * vYell[i,s,t,c])
AC[i,s,t,c,3] = (acw[i,s,c] * vPush[i,s,t,c]) + ((1-acw[i,s,c]) * vBlue[i,s,t,c])
AC[i,s,t,c,4] = (acw[i,s,c] * vPush[i,s,t,c]) + ((1-acw[i,s,c]) * vYell[i,s,t,c])
# choice probability for each action-color combination
pAC[i,s,t,c,1] = exp(beta[i,s]*AC[i,s,t,c,1])/(exp(beta[i,s]*AC[i,s,t,c,2])+exp(beta[i,s]*AC[i,s,t,c,3])+exp(beta[i,s]*AC[i,s,t,c,4]))
pAC[i,s,t,c,2] = exp(beta[i,s]*AC[i,s,t,c,2])/(exp(beta[i,s]*AC[i,s,t,c,1])+exp(beta[i,s]*AC[i,s,t,c,3])+exp(beta[i,s]*AC[i,s,t,c,4]))
pAC[i,s,t,c,3] = exp(beta[i,s]*AC[i,s,t,c,3])/(exp(beta[i,s]*AC[i,s,t,c,1])+exp(beta[i,s]*AC[i,s,t,c,2])+exp(beta[i,s]*AC[i,s,t,c,4]))
pAC[i,s,t,c,4] = exp(beta[i,s]*AC[i,s,t,c,4])/(exp(beta[i,s]*AC[i,s,t,c,1])+exp(beta[i,s]*AC[i,s,t,c,2])+exp(beta[i,s]*AC[i,s,t,c,3]))
# realizing choice
y[i,s,t,c] ~ dcat(pAC[i,s,t,c,1:4]) #THIS DOESN'T WORK BECAUSE 4D ARRAY
}
}
# PRIORS
for(c in 1:nConds){
alpha[i,s,c] ~ dbeta(1,1)
acw[i,s,c] ~ dbeta(1,1)
} #end condition specific priors
beta[i,s] ~ dnorm(.001,0.03) T(0.0001,.045)
} #end session loop
} #end subject loop
} #end model

First off Jags uses NA, not NaN, and the NA can appear in the right-hand side of an equation, it just cannot be a parameter for a random variable.
In relation to the array issue, code wise it is a lot to work through, and I don't have a great idea of what your code is trying to do, but one great solution conceptually within JAGS when working with > 2d data you are passing in, is to figure out how to represent that same data within a matrix. Sometimes referred to as making wide data long. So in this case you would create a matrix, and if I understand correctly you would have one for the stimulus, and one for the action taken. There would be (n x 2 x 4 x 42) rows and the columns would be the stimulus/action (depending on the table) in column 1, the subject in column 2, session in column 3, and so on. You then only need 1 loop in JAGS to go from 1:nrow of this matrix and can just select variables from the respective columns as you need them.
The series of ifelse cases is I think a separate problem not solved by the above solution.
For example, in
y[i,s,1:nTrials,c] = ifelse(c ==1,yAct1,ifelse(c==2,yAct2,ifelse(c==3,yStim1,yStim2)))
If I am understanding that correctly you are assigning a certain variable (some yAct or yStim) to the array based on c. Instead create a vector
X=c(yAct1,yAct2,yStim1,yStim2) where c here is to create a vector, not your variable c,
and assign to the matrix by indexing X with c
y[i,s,1:nTrials,c]= X[c]
I know these answers won't help fully solve your code since there was too much to go through there, but hopefully this conceptually helps you understand what improvements could be made. Let me know if you need anymore help.

Related

should I use basinhopping instead of minimize to find colors based on a small set of transforms?

question up front
def shade_func(color, offset):
return tuple([int(c * (1 - offset)) for c in color])
def tint_func(color, offset):
return tuple([int(c + (255 - c) * offset) for c in color])
def tone_func(color, offset):
return tuple([int(c * (1 - offset) + 128 * offset) for c in color])
given an objective over a collection of colors that returns the least distance to a target color, how do I ensure that basinhopping isn't better than minimization in scikit learn?
I was thinking that, for any one color, there will be up to 4 moments in a v-shaped curve, and so only one minimum. if the value with offset zero is itself a minimum, maybe it could be 5. Am I wrong? In any case each is a single optimum, so if we are only searching one color at a time, no reason to use basinhopping.
If we instead use basinhopping to scan all colors at once (we can scale the two different dimensions, in fact this is where the idea of a preprocessor function first came from), it scans them, but does not do such a compelling just of scanning all colors. Some colors it only tries once. I think it might completely skip some colors with large enough sets.
details
I was inspired by way artyclick shows colors and allows searching for them. If you look at an individual color, for example mauve you'll notice that it prominently displays the shades, tints, and tones of the color, rather like an artist might like. If you ask it for the name of a color, it will use a hidden unordered list of about a thousand color names, and some javascript to find the nearest colorname to the color you chose. In fact it will also show alternatives.
I noticed that quite often a shade, tint or tone of an alternative (or even the best match) was often a better match than the color it provided. For those who don't know about shade, tint and tone, there's a nice write up at Dunn Edward's Paints. It looks like shade and tint are the same but with signs reversed, if doing this on tuples representing colors. For tone it is different, a negative value would I think saturate the result.
I felt like there must be authoritative (or at least well sourced) colorname sources it could be using.
In terms of the results, since I want any color or its shade/tint/tone, I want a result like this:
{'color': '#aabbcc',
'offset': {'type': 'tint', 'value': 0.31060384614807254}}
So I can return the actual color name from the color, plus the type of color transform to get there and the amount of you have to go.
For distance of colors, there is a great algorithm that is meant to model human perception that I am using, called CIEDE 2000. Frankly, I'm just using a snippet I found that implements this, it could be wrong.
So now I want to take in two colors, compare their shade, tint, and tone to a target color, and return the one with the least distance. After I am done, I can reconstruct if it was a shade, tint or tone transform from the result just by running all three once and choosing the best fit. With that structure, I can iterate over every color, and that should do it. I use optimization because I don't want to hard code what offsets it should consider (though I am reconsidering this choice now!).
because I want to consider negatives for tone but not for shade/tint, my objective will have to transform that. I have to include two values to optimize, since the objection function will need to know what color to transform (or else the result will give me know way of knowing which color to use the offset with).
so my call should look something like the following:
result = min(minimize(objective, (i,0), bounds=[(i, i), (-1, 1)]) for i in range(len(colors)))
offset_type = resolve_offset_type(result)
with that in mind, I implemented this solution, over the past couple of days.
current solution
from scipy.optimize import minimize
import numpy as np
import math
def clamp(low, x, high):
return max(low, min(x, high))
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def rgb_to_hex(rgb):
return '#{:02x}{:02x}{:02x}'.format(*rgb)
def rgb_to_lab(color):
# Convert RGB to XYZ color space
R = color[0] / 255.0
G = color[1] / 255.0
B = color[2] / 255.0
R = ((R + 0.055) / 1.055) ** 2.4 if R > 0.04045 else R / 12.92
G = ((G + 0.055) / 1.055) ** 2.4 if G > 0.04045 else G / 12.92
B = ((B + 0.055) / 1.055) ** 2.4 if B > 0.04045 else B / 12.92
X = R * 0.4124 + G * 0.3576 + B * 0.1805
Y = R * 0.2126 + G * 0.7152 + B * 0.0722
Z = R * 0.0193 + G * 0.1192 + B * 0.9505
return (X,Y,Z)
def shade_func(color, offset):
return tuple([int(c * (1 - offset)) for c in color])
def tint_func(color, offset):
return tuple([int(c + (255 - c) * offset) for c in color])
def tone_func(color, offset):
return tuple([int(c * (1 - offset) + 128 * offset) for c in color])
class ColorNameFinder:
def __init__(self, colors, distance=None):
if distance is None:
distance = ColorNameFinder.ciede2000
self.distance = distance
self.colors = [hex_to_rgb(color) for color in colors]
#classmethod
def euclidean(self, left, right):
return (left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2 + (left[2] - right[2]) ** 2
#classmethod
def ciede2000(self, color1, color2):
# Convert color to LAB color space
lab1 = rgb_to_lab(color1)
lab2 = rgb_to_lab(color2)
# Compute CIE 2000 color difference
C1 = math.sqrt(lab1[1] ** 2 + lab1[2] ** 2)
C2 = math.sqrt(lab2[1] ** 2 + lab2[2] ** 2)
a1 = math.atan2(lab1[2], lab1[1])
a2 = math.atan2(lab2[2], lab2[1])
dL = lab2[0] - lab1[0]
dC = C2 - C1
dA = a2 - a1
dH = 2 * math.sqrt(C1 * C2) * math.sin(dA / 2)
L = 1
C = 1
H = 1
LK = 1
LC = math.sqrt(math.pow(C1, 7) / (math.pow(C1, 7) + math.pow(25, 7)))
LH = math.sqrt(lab1[0] ** 2 + lab1[1] ** 2)
CB = math.sqrt(lab2[1] ** 2 + lab2[2] ** 2)
CH = math.sqrt(C2 ** 2 + dH ** 2)
SH = 1 + 0.015 * CH * LC
SL = 1 + 0.015 * LH * LC
SC = 1 + 0.015 * CB * LC
T = 0.0
if (a1 >= a2 and a1 - a2 <= math.pi) or (a2 >= a1 and a2 - a1 > math.pi):
T = 1
else:
T = 0
dE = math.sqrt((dL / L) ** 2 + (dC / C) ** 2 + (dH / H) ** 2 + T * (dC / SC) ** 2)
return dE
def __factory_objective(self, target, preprocessor=lambda x: x):
def fn(x):
print(x, preprocessor(x))
x = preprocessor(x)
color = self.colors[x[0]]
offset = x[1]
bound_offset = abs(offset)
offsets = [
shade_func(color, bound_offset),
tint_func(color, bound_offset),
tone_func(color, offset)]
least_error = min([(right, self.distance(target, right)) \
for right in offsets], key = lambda x: x[1])[1]
return least_error
return fn
def __resolve_offset_type(self, sample, target, offset):
bound_offset = abs(offset)
shade = shade_func(sample, bound_offset)
tint = tint_func(sample, bound_offset)
tone = tone_func(sample, offset)
lookup = {}
lookup[shade] = "shade"
lookup[tint] = "tint"
lookup[tone] = "tone"
offsets = [shade, tint, tone]
least_error = min([(right, self.distance(target, right)) for right in offsets], key = lambda x: x[1])[0]
return lookup[least_error]
def nearest_color(self, target):
target = hex_to_rgb(target)
preprocessor=lambda x: (int(x[0]), x[1])
result = min(\
[minimize( self.__factory_objective(target, preprocessor=preprocessor),
(i, 0),
bounds=[(i, i), (-1, 1)],
method='Powell') \
for i, color in enumerate(self.colors)], key=lambda x: x.fun)
color_index = int(result.x[0])
nearest_color = self.colors[color_index]
offset = preprocessor(result.x)[1]
offset_type = self.__resolve_offset_type(nearest_color, target, offset)
return {
"color": rgb_to_hex(nearest_color),
"offset": {
"type": offset_type,
"value": offset if offset_type == 'tone' else abs(offset)
}
}
let's demonstrate this with mauve. We'll define a target that is similar to a shade of mauve, include mauve in a list of colors, and ideally we'll get mauve back from our test.
colors = ['#E0B0FF', '#FF0000', '#000000', '#0000FF']
target = '#DFAEFE'
agent = ColorNameFinder(colors)
agent.nearest_color(target)
we do get mauve back:
{'color': '#e0b0ff',
'offset': {'type': 'shade', 'value': 0.0031060384614807254}}
the distance is 0.004991238317138219
agent.distance(hex_to_rgb(target), shade_func(hex_to_rgb(colors[0]), 0.0031060384614807254))
why use Powell's method?
in this arrangement, it is simply the best. No other method that uses bounds did a good job of scanning positives and negatives, and I had mixed results using the preprocessor to scale the values back to negative with bounds of (0,2).
I do notice that in the sample test, a range between about 0.003 and 0.0008 seems to produce the same distance, and that the values my approach considers includes a large number of these. is there a more efficient solution?
If I'm wrong, please let me know.
correctness of the color transformations
what is adding a negative amount of white? (in the case of a tint) I was thinking it is like adding a positive amount of black -- ie a shade, with signs reversed.
my implementation is not correct:
agent.distance(hex_to_rgb(target), shade_func(hex_to_rgb(colors[0]), 0.1)) - agent.distance(hex_to_rgb(target), tint_func(hex_to_rgb(colors[0]), -0.1))
produces 0.3239904390784106 instead of 0.
I'll probably be fixing that soon

How to solve a binary optimization problem with linear programming in ortools / cpmodel?

I'm trying to optimize a binary problem for a website of mine.
The data contains roughly 75 items and each of the items has a weight (between 50 and 1000) and a price attached to it. Here's a data snippet:
{"weighting":{
"0":500,
"1":50,
"2":50,
"3":50,
"4":250,
"5":1000
},
"price":{
"0":4,
"1":78,
"2":75,
"3":170,
"4":5,
"5":4
}
}
I calculate the expected value of the whole data set with
exp_val = (w1 p1 + w2 p2 + ... + wn pn) / sum(w1 + w2 + ... wn)
with
sum(w1 + w2 + ... wn) = 23665 (considering all items)
So far so good, but now comes the tricky part. Not all items are desired, that is, they are worth less and / or have a high weighting which dilutes the pool I can draw from.
By "blocking" or removing up to 3 items I can draw from the remaining items only, and by doing so maximizing the expedted value function. The question is: Which items to remove? As the prices vary over time, I have to check the items to remove on a regular basis.
I have started by simply removing the items with the highest weights and the smallest price, but I'm sure this only represents a local optimum and there would be a more optimal strategy.
After checking some websites, it seems that Mixed-integer linear programming (MILP) or in particular BILP (binary ...) can solve my issue, and I found https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.milp.html but wasn't able to make it work, as I'm stuck translating my problem into code. Can anyone help me out?
I've tried the following:
# new data!
data = {"w_default": {
"0": 500,
"1": 250,
"2": 500,
"3": 1000,
"4": 1000,
"5": 500
},
"chaos": {
"0": 8,
"1": 5,
"2": 5,
"3": 4,
"4": 4,
"5": 2
}
}
UB = 10000000
model = cp.CpModel()
num_items = len(data["w_default"])
# create boolean coefficients
dv_select_items = {i: model.NewBoolVar("item_" + str(i)) for i in data["w_default"]}
# constraint: remove exactly 3 items
# TODO: remove exactly 3 items because right now the objective function does not work yet (even without removing any items)
model.Add(sum(dv_select_items[i] for i in dv_select_items) == num_items)
##### numerator equation #####
# x_i * w_i * p_i
xi_wi_pi = model.NewIntVar(0, UB, "xi_wi_pi")
model.Add(xi_wi_pi == sum(dv_select_items[i] * data["w_default"][i] * data["chaos"][i] for i in dv_select_items))
##### denominator equation #####
xi_wi = model.NewIntVar(0, UB, "xi_wi")
model.Add(xi_wi == sum(dv_select_items[i] * data["w_default"][i] for i in dv_select_items))
y_xi_wi_pi = model.NewIntVar(0, UB, "y_xi_wi_pi")
model.AddDivisionEquality(y_xi_wi_pi, xi_wi_pi, xi_wi)
# set target
model.Maximize(xi_wi_pi)
solver = cp.CpSolver()
solver.Solve(model)
# inspect the solution
objective_function_value = solver.ObjectiveValue()
solution_info = solver.SolutionInfo()
result = [(i, solver.Value(dv_select_items[i])) for i in dv_select_items]
It seems that the problem lies within this part:
##### denominator equation #####
xi_wi = model.NewIntVar(0, UB, "xi_wi")
model.Add(xi_wi == sum(dv_select_items[i] * data["w_default"][i] for i in dv_select_items))
y_xi_wi_pi = model.NewIntVar(0, UB, "y_xi_wi_pi")
model.AddDivisionEquality(y_xi_wi_pi, xi_wi_pi, xi_wi)
No matter how I rephrase this (tried #Bhartendu Awasthi proposal with
model.AddMultiplicationEquality(y_x_i_w_i, [y_x_i_w_i, y])
first but the objective function always returns 0, although it should be (without removing anything)
xi_wi_pi = 16750
xi_wi = 3750
objective_function_value = 16750 / 3750 = 4,466
If we exclude items 3, 4 and 5 the result should be
objective_function_value = 7750 / 1250 = 6,2
I have no solution at the moment but will try to solve it in the next days.
Leveraging #joni's comments, I have translated it into code. Multiplication of decision variables is not linear, and you will have to linearize the products by adding another intermediate continuous decision variable.
However, if you use Google-ortool's CP-SAT solver (which I have used), it can handle a few non-linear operations such multiplication, division etc of decision variables, which a pure linear solver would not support.
Code listing
from ortools.sat.python import cp_model as cp
model = cp.CpModel()
data = {"weighting":{
"0":500,
"1":50,
"2":50,
"3":50,
"4":250,
"5":1000
},
"price":{
"0":4,
"1":78,
"2":75,
"3":170,
"4":5,
"5":4
}
}
num_items = len(data["weighting"])
dv_select_items = {i : model.NewBoolVar("item_" + i) for i in data["weighting"]}
# constraint : keep only 3 items
model.Add(sum(dv_select_items[i] for i in dv_select_items) == 3)
y = model.NewIntVar(0, 1000000, "")
x_i_w_i_p_i = model.NewIntVar(0, 1000000, "") # x_i * w_i * p_i
model.Add(x_i_w_i_p_i == sum(dv_select_items[i] * data["weighting"][i] * data["price"][i] for i in dv_select_items))
y_x_i_w_i_p_i = model.NewIntVar(0, 1000000000000, "") # y * x_i * w_i * p_i
model.AddMultiplicationEquality(y_x_i_w_i_p_i, [x_i_w_i_p_i, y])
# 1 = sum(y * x_i * w_i) # constraint
x_i_w_i = model.NewIntVar(0, 1000000, "")
model.Add(x_i_w_i == sum(dv_select_items[i] * data["weighting"][i] for i in dv_select_items))
y_x_i_w_i = model.NewIntVar(0, 1000000000000, "")
model.AddMultiplicationEquality(y_x_i_w_i, [x_i_w_i, y])
remaining = model.NewIntVar(0, 10000, "")
model.Add(1000000 == y_x_i_w_i + remaining)
# 1 = sum(y * x_i * w_i)
# with above y will be fractional. Now, CP-SAT solver does not support
# fractional values. With using integer values (for y) there is no
# gurantee that 1 = sum(y * x_i * w_i), hence adding "remaining" part
# but we should always try to make "remaining" as close to zero
model.Maximize(y_x_i_w_i_p_i)
solver = cp.CpSolver()
solver.Solve(model)
model.Minimize(remaining)
model.Add(y_x_i_w_i_p_i >= int(solver.ObjectiveValue()))
solver = cp.CpSolver()
solver.Solve(model)
# inspect the solution
objective_function_value = solver.ObjectiveValue()
[(i, solver.Value(dv_select_items[i])) for i in dv_select_items]
# 2st, 3th and 4th item gets selected
Here's my solution:
1. Make the results trackable
In cpmodel from ortools you can do so by introducing a class with
from ortools.sat.python import cp_model as cp
class VarArraySolutionPrinter(cp.CpSolverSolutionCallback):
"""Print intermediate solutions."""
def __init__(self, variables):
cp.CpSolverSolutionCallback.__init__(self)
self.__variables = variables
self.__solution_count = 0
def on_solution_callback(self):
self.__solution_count += 1
for v in self.__variables:
print('%s=%i' % (v, self.Value(v)), end=' ')
print()
def solution_count(self):
return self.__solution_count
and than structure your code like this
# ############################################
# content of your objective function goes here
# ############################################
# solve the model
solver = cp.CpSolver()
status = solver.Solve(model)
# https://developers.google.com/optimization/cp/cp_solver?hl=de
# debugging
solution_printer = VarArraySolutionPrinter([objective, xi_wi_pi, xi_wi])
solver.parameters.enumerate_all_solutions = True
# inspect the solution
objective_function_value = solver.ObjectiveValue()
solution_info = solver.SolutionInfo()
status = solver.Solve(model, solution_printer)
note that in
solution_printer = VarArraySolutionPrinter([objective, xi_wi_pi, xi_wi])
you want to add the variable names, i.e. using the third argument (str) when creating a variable:
xi_wi = model.NewIntVar(0, 100, "xi_wi")
2. Create the model
With that out of the way I found that I didn't had to follow Jonis advice because cpmodel from ortool can handel binary variables itself. This code works for me:
from ortools.sat.python import cp_model as cp
# w_default = weighting
# chaos = price
data = {"w_default":{
"0":500,
"1":50,
"2":50,
"3":50,
"4":250,
"5":1000
},
"chaos":{
"0":4,
"1":78,
"2":75,
"3":170,
"4":5,
"5":4
}
}
model = cp.CpModel()
num_items = len(data["w_default"])
# create boolean coefficients
dv_select_items = {i: model.NewBoolVar("item_" + str(i)) for i in data["w_default"]}
# constraint: remove exactly 3 items
model.Add(sum(dv_select_items[i] for i in dv_select_items) == num_items - 3)
# ##### numerator equation #####
constant = 1000
# x_i * w_i * p_i // sum of weightings * prices = 200.000 -> UB 500.000 to give some space?
xi_wi_pi = model.NewIntVar(50000 * constant, 500000 * constant, "xi_wi_pi")
model.Add(xi_wi_pi == sum(
dv_select_items[i] * data["w_default"][i] * data["chaos"][i] * constant for i in dv_select_items))
##### denominator equation #####
# xi_wi // sum of weightings 23665: 20665 with 3 blocked
lb_weights = int(tot_weight * 0.75)
xi_wi = model.NewIntVar(lb_weights, tot_weight, "xi_wi")
model.Add(xi_wi == sum(dv_select_items[i] * data["w_default"][i] for i in dv_select_items))
objective = model.NewIntVar(0, 100 * constant, "objective")
model.AddDivisionEquality(objective, xi_wi_pi, xi_wi)
# set target
model.Maximize(objective)
# solve the model
solver = cp.CpSolver()
status = solver.Solve(model)
# https://developers.google.com/optimization/cp/cp_solver?hl=de
# debugging
solution_printer = VarArraySolutionPrinter([objective, xi_wi_pi, xi_wi])
solver.parameters.enumerate_all_solutions = True
# inspect the solution
objective_function_value = solver.ObjectiveValue()
solution_info = solver.SolutionInfo()
status = solver.Solve(model, solution_printer)
3. Scale the nominator because AddDivisionEquality round to integers
You might be wondering why I have added a constant in the code (it doesn't work without it). This is because
model.AddDivisionEquality(objective, xi_wi_pi, xi_wi)
always rounds the result to an integer value and because the results are in the range of 8.something the objective function always returned 8. However, if you multiply the numerator with 1000, 8.3456 now becomes 8345 and 8.4334 becomes 8433 and thus can be evaluated correctly.
Hope this helps anyone with a similar problem. Also, many thanks to Joni and Barthendu for pointing me in the right direction!

Different shape arrays operations

A bit of background:
I want to calculate the array factor of a MxN antenna array, which is given by the following equation:
Where w_i are the complex weight of the i-th element, (x_i,y_i,z_i) is the position of the i-th element, k is the wave number, theta and phi are the elevation and azimuth respectively, and i ranges from 0 to MxN-1.
In the code I have:
-theta and phi are np.mgrid with shape (200,200) each,
-w_i, and (x,y,z)_i are np.array with shape (NxM,) each
so AF is a np.array with shape (200,200) (sum over i).There is no problem so far, and I can get AF easily doing:
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
Now, each w_i depends on frequency, so AF too, and now I have w_i with shape (NxM,1000) (I have 1000 samples of each w_i in frequency). I tried to use the above code changing
af = zeros([1000,theta.shape[0],phi.shape[0]])
but I get 'operands could not be broadcast together'. I can solve this by using a for loop through the 1000 values, but it is slow and is a bit ugly. So, what is the correct way to do the summation, or the correct way to properly define w_i and AF ?
Any help would be appreciated. Thanks.
edit
The code with the new dimension I'm trying to add is the next:
from numpy import *
class AntennaArray:
def __init__(self,f,asize=None,tipo=None,dx=None,dy=None):
self.Lambda = 299792458 / f
self.k = 2*pi/self.Lambda
self.size = asize
self.type = tipo
self._AF_DATA_SIZE = 200
self.theta,self.phi = mgrid[0 : pi : self._AF_DATA_SIZE*1j,0 : 2*pi : self._AF_DATA_SIZE*1j]
self.element_pos = None
self.element_amp = None
self.element_pha = None
if dx == None:
self.dx = self.Lambda/2
else:
self.dx = dx
if dy == None:
self.dy = self.Lambda/2
else:
self.dy = dy
self.generate_array()
def generate_array(self):
M = self.size[0]
N = self.size[1]
dx = self.dx
dy = self.dy
x_pos = arange(0,dx*N,dx)
y_pos = arange(0,dy*M,dy)
z_pos = 0
ele = zeros([N*M,3])
for i in range(M):
ele[i*N:(i+1)*N,0] = x_pos[:]
for i in range(M):
ele[i*N:(i+1)*N,1] = y_pos[i]
self.element_pos = ele
#self.array_factor = self.calculate_array_factor()
def calculate_array_factor(self):
theta,phi = self.theta,self.phi
k = self.k
x_pos = self.element_pos[:,0]
y_pos = self.element_pos[:,1]
z_pos = self.element_pos[:,2]
w = self.element_amp*exp(1j*self.element_pha)
if len(self.element_pha.shape) > 1:
#I have f_size samples of w_i(f)
f_size = self.element_pha.shape[1]
af = zeros([f_size,theta.shape[0],phi.shape[0]])
else:
#I only have w_i
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
**strong text**#This for loop does the summation over i
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
return af
I tried to test it with the next main
from numpy import *
f_points = 10
M = 2
N = 2
a = AntennaArray(5.8e9,[M,N])
a.element_amp = ones([M*N,f_points])
a.element_pha = zeros([M*N,f_points])
af = a.calculate_array_factor()
But I get
ValueError: 'operands could not be broadcast together with shapes (10,) (200,200) '
Note that if I set
a.element_amp = ones([M*N])
a.element_pha = zeros([M*N])
This works well.
Thanks.
I had a look at the code, and I think this for loop:
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
is wrong in many ways. You are mixing dimensions, you cannot loop that way.
And by the way, to make full use of numpy efficiency, never loop over the arrays. It slows down the execution significantly.
I tried to rework that part.
First, I advice you to not use from numpy import *, it's bad practice (see here). Use import numpy as np. I reintroduced the np abbreviation, so you can understand what comes from numpy.
Frequency independent case
This first snippet assumes that w is a 1D array of length 4: I am neglecting the frequency dependency of w, to show you how you can get what you already obtained without the for loop and using instead the power of numpy.
af_points = w[:,np.newaxis,np.newaxis]*np.e**(-1j*
(k * x_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.cos(phi) +
k * y_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.sin(phi) +
k * z_pos[:,np.newaxis,np.newaxis]*np.cos(theta)
))
af = np.sum(af_points, axis=0)
I am using numpy broadcasting to obtain a 3D array named af_points, whose shape is (4, 200, 200). To do it, I use np.newaxis to extend the number of axis of an array in order to use broadcasting correctly. More here on np.newaxis.
So, w[:,np.newaxis,np.newaxis] is an array of shape (4, 1, 1). Similarly for x_pos[:,np.newaxis,np.newaxis], y_pos[:,np.newaxis,np.newaxis] and z_pos[:,np.newaxis,np.newaxis]. Since the angles have shape (200, 200), broadcasting can be done, and af_points has shape (4, 200, 200).
Finally the sum is done by np.sum, summing over the first axis to obtain a (200, 200) array.
Frequency dependent case
Now w has shape (4, 10), where 10 are the frequency points. The idea is the same, just consider that the frequency is an additional dimension in your numpy arrays: now af_points will be an array of shape (4, 10, 200, 200) where 10 are the f_points you have defined.
To keep it understandable, I've split the calculation:
#exp_point is only the exponent, frequency independent. Will be a (4, 200, 200) array.
exp_points = np.e**(-1j*
(k * x_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.cos(phi) +
k * y_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.sin(phi) +
k * z_pos[:,np.newaxis,np.newaxis]*np.cos(theta)
))
af_points = w[:,:,np.newaxis,np.newaxis] * exp_points[:,np.newaxis,:,:]
af = np.sum(af_points, axis=0)
And now af has shape (10, 200, 200).

Determening begin parameters 2D gaussian fit

I'm working on some code which needs to be able to preform a 2d gaussian fitting. I mostly based my code on following question: Fitting a 2D Gaussian function using scipy.optimize.curve_fit - ValueError and minpack.error . Now is problem that I don't really have an initial guess about the different parameters that need to be used.
I've tried this:
def twoD_Gaussian(x_data_tuple, amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
(x,y) = x_data_tuple
xo = float(xo)
yo = float(yo)
a = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
b = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
c = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
g = offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo)
+ c*((y-yo)**2)))
return g.ravel()
The data.reshape(201,201) is just something I took from the aformentioned question.
mean_gauss_x = sum(x * data.reshape(201,201)) / sum(data.reshape(201,201))
sigma_gauss_x = np.sqrt(sum(data.reshape(201,201) * (x - mean_gauss_x)**2) / sum(data.reshape(201,201)))
mean_gauss_y = sum(y * data.reshape(201,201)) / sum(data.reshape(201,201))
sigma_gauss_y = np.sqrt(sum(data.reshape(201,201) * (y - mean_gauss_y)**2) / sum(data.reshape(201,201)))
initial_guess = (np.max(data), mean_gauss_x, mean_gauss_y, sigma_gauss_x, sigma_gauss_y,0,10)
popt, pcov = curve_fit(twoD_Gaussian, (x, y), data, p0=initial_guess)
data_fitted = twoD_Gaussian((x, y), *popt)
If I try this, I get following error message: ValueError: setting an array element with a sequence.
Is the reasoning about the begin parameters correct?
And why do I get this error?
If I use the runnable code from the linked question and substitute your definition of initial_guess:
mean_gauss_x = sum(x * data.reshape(201,201)) / sum(data.reshape(201,201))
sigma_gauss_x = np.sqrt(sum(data.reshape(201,201) * (x - mean_gauss_x)**2) / sum(data.reshape(201,201)))
mean_gauss_y = sum(y * data.reshape(201,201)) / sum(data.reshape(201,201))
sigma_gauss_y = np.sqrt(sum(data.reshape(201,201) * (y - mean_gauss_y)**2) / sum(data.reshape(201,201)))
initial_guess = (np.max(data), mean_gauss_x, mean_gauss_y, sigma_gauss_x, sigma_gauss_y,0,10)
Then
print(inital_guess)
yields
(13.0, array([...]), array([...]), array([...]), array([...]), 0, 10)
Notice that some of the values in initial_guess are arrays. The optimize.curve_fit function expects initial_guess to be a tuple of scalars. This is the source of the problem.
The error message
ValueError: setting an array element with a sequence
often arises when an array-like is supplied when a scalar value is expected. It is a hint that the source of the problem may have to do with an array having the wrong number of dimensions. For example, it might arise if you pass a 1D array to a function that expects a scalar.
Let's look at this piece of code taken from the linked question:
x = np.linspace(0, 200, 201)
y = np.linspace(0, 200, 201)
X, Y = np.meshgrid(x, y)
x and y are 1D arrays, while X and Y are 2D arrays. (I've capitalized all 2D arrays to help distinguish them from 1D arrays).
Now notice that Python sum and NumPy's sum method behave differently when applied to 2D arrays:
In [146]: sum(X)
Out[146]:
array([ 0., 201., 402., 603., 804., 1005., 1206., 1407.,
1608., 1809., 2010., 2211., 2412., 2613., 2814., 3015.,
...
38592., 38793., 38994., 39195., 39396., 39597., 39798., 39999.,
40200.])
In [147]: X.sum()
Out[147]: 4040100.0
The Python sum function is equivalent to
total = 0
for item in X:
total += item
Since X is a 2D array, the loop for item in X is iterating over the rows of X. Each item is therefore a 1D array representing a row of X. Thus, total ends up being a 1D array.
In contrast, X.sum() sums all the elements in X and returns a scalar.
Since initial_guess should be a tuple of scalars,
everywhere you use sum you should instead use the NumPy sum method. For example, replace
mean_gauss_x = sum(x * data) / sum(data)
with
mean_gauss_x = (X * DATA).sum() / (DATA.sum())
import numpy as np
import scipy.optimize as optimize
import matplotlib.pyplot as plt
# define model function and pass independant variables x and y as a list
def twoD_Gaussian(data, amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
X, Y = data
xo = float(xo)
yo = float(yo)
a = (np.cos(theta) ** 2) / (2 * sigma_x ** 2) + (np.sin(theta) ** 2) / (
2 * sigma_y ** 2
)
b = -(np.sin(2 * theta)) / (4 * sigma_x ** 2) + (np.sin(2 * theta)) / (
4 * sigma_y ** 2
)
c = (np.sin(theta) ** 2) / (2 * sigma_x ** 2) + (np.cos(theta) ** 2) / (
2 * sigma_y ** 2
)
g = offset + amplitude * np.exp(
-(a * ((X - xo) ** 2) + 2 * b * (X - xo) * (Y - yo) + c * ((Y - yo) ** 2))
)
return g.ravel()
# Create x and y indices
x = np.linspace(0, 200, 201)
y = np.linspace(0, 200, 201)
X, Y = np.meshgrid(x, y)
# create data
data = twoD_Gaussian((X, Y), 3, 100, 100, 20, 40, 0, 10)
data_noisy = data + 0.2 * np.random.normal(size=data.shape)
DATA = data.reshape(201, 201)
# add some noise to the data and try to fit the data generated beforehand
mean_gauss_x = (X * DATA).sum() / (DATA.sum())
sigma_gauss_x = np.sqrt((DATA * (X - mean_gauss_x) ** 2).sum() / (DATA.sum()))
mean_gauss_y = (Y * DATA).sum() / (DATA.sum())
sigma_gauss_y = np.sqrt((DATA * (Y - mean_gauss_y) ** 2).sum() / (DATA.sum()))
initial_guess = (
np.max(data),
mean_gauss_x,
mean_gauss_y,
sigma_gauss_x,
sigma_gauss_y,
0,
10,
)
print(initial_guess)
# (13.0, 100.00000000000001, 100.00000000000001, 57.106515650488404, 57.43620227324201, 0, 10)
# initial_guess = (3,100,100,20,40,0,10)
popt, pcov = optimize.curve_fit(twoD_Gaussian, (X, Y), data_noisy, p0=initial_guess)
data_fitted = twoD_Gaussian((X, Y), *popt)
fig, ax = plt.subplots(1, 1)
ax.imshow(
data_noisy.reshape(201, 201),
cmap=plt.cm.jet,
origin="bottom",
extent=(X.min(), X.max(), Y.min(), Y.max()),
)
ax.contour(X, Y, data_fitted.reshape(201, 201), 8, colors="w")
plt.show()

Calculate all compound growth rates from a list of values

I have a list of growth rates and would like to calculate all available compounded growth rates:
l = [0.3, 0.2, 0.1]
Output (as a list):
o = [0.56, 0.716]
calculation detail about the compounded growth rates:
0.56 = (1 + 0.3) * (1 + 0.2) - 1
0.716 = (1 + 0.3) * (1 + 0.2) * (1 + 0.1) - 1
The function should be flexible to the length of the input list.
You could express the computation with list comprehensions / generator expressions and using itertools.accumulate to handle the compounding:
import itertools as IT
import operator
def compound_growth_rates(l):
result = [xy-1 for xy in
IT.islice(IT.accumulate((1+x for x in l), operator.mul), 1, None)]
return result
l = [0.3, 0.2, 0.1]
print(compound_growth_rates(l))
prints
[0.56, 0.7160000000000002]
Or, equivalently, you could write this with list-comprehensions and a for-loop:
def compound_growth_rates(l):
add_one = [1+x for x in l]
products = [add_one[0]]
for x1 in add_one[1:]:
products.append(x1*products[-1])
result = [p-1 for p in products[1:]]
return result
I think the advantage of using itertools.accumulate is that it expresses the
intent of the code better than the for-loop. But the for-loop may be more
readable in the sense that it uses more commonly known syntax.

Resources