PV Overproduction within a linear cost factor optimization - python

So I am currently trying to optimize the costs for energy in a household. The optimization is based on a cost factor function which I am trying to minimize.
model = ConcreteModel()
model.t = RangeSet(0, 8759)
def costs(model, t):
return sum(model.cost_factor[t] * model.elec_grid[t] for t in model.t)
model.costs = Objective(rule = costs, sense = minimize)
Due to pv overproduction being a thing I try to negate by using these functions:
model.elec_consumption = Param(model.t, initialize = df['Consumption'])
model.pv = Param(model.t, initialize = df['PV'])
model.excess_pv = Var(model.t, within = NonNegativeReals, initialize = 0)
model.demand = Var(model.t, initialize = 0, within = NonNegativeReals)
def pv_overproduction(model, t):
return model.excess_pv[t] >= model.pv[t] - model.demand[t]
model.pv_overproduction = Constraint(model.t, rule = pv_overproduction)
def lastdeckung(model, t):
return (model.pv[t] - model.excess_pv[t]) + model.elec_grid[t] == model.demand[t]
model.lastdeckung = Constraint(model.t, rule = lastdeckung)
The problem is when the cost factor is negative the optimizer puts model.excess_pv very high so he can crank up the model.elec_grid variable in an effort to minimize the cost factor.
That is obviously not the intention but so far I wasnt able to find a better way to calculate the excess pv. An easy fix would technically be to just have a cost factor which is constantly positive but sadly thats not an option.
I'd appreciate if someone had an idea how to fix this.
The basics are that I want to maximize the usage of the pv electricity in order to reduce costs. At some points there is to mooch pv in the system so in order for that optimization to still work I need to get rid of the excess.
return model.demand[t] == model.elec_consumption[t]
model.demand_rule = Constraint(model.t, rule = demand_rule)
This is the demand. Technically there are more functions but for the the problem solving that is irrelevant. The main problem is that this function doesnt work due to the cost factor being negative sometimes
model.excess_pv[t] >= model.pv[t] - model.demand[t]
Excess_pv aswell as model.demand are variables wheres model.pv is a parameter.
So as far as I got in my problemsearching I need to change my overproduction function in a way that it uses the value from pv - excess_pv if the value is > 0 and should the value be < = 0 its supposed to be zero.

I think the easiest way to do this is to probably just penalize excess production to a greater extent than the maximally negative cost factor.
Why can't you...
excess_pentalty = max(-min(cost) + epsilon, 0) # use maximin to prevent odd behavior if there is no negative cost, which might lead to a negative penalty...
# make obj from components, so we can inspect true cost (w/o penalty) later...
cost = sum(model.cost_factor[t] * model.elec_grid[t] for t in model.t)
overproduction_pentaly = sum(excess_penalty * model.excess_pv[t] for t in model.t)
model.obj = Objective(expr= cost + overproduction_penalty, sense = minimize)
and later if you want the cost independently, you can just check the value of cost, which is a legal pyomo expression.
value(cost)
I think you could also add the expression as a model component, if that is important...
model.cost = ...
model.overproduction_penalty = ...

So the idea of a piecewise function is definitely an option for the problem mentioned in this post. It is quite a fancy and complicated solution though. The idea of penalties is much easier and it also showed a few more flaws in my code. Due to negative cost factor the optimizer tries to maximize grid input which is not wrong but when some variables are not capped the optimizer uses electricity with no efficiency whatsoever. So easiest way as mentionted earlier is to just penalize the grid import from the beginning so there are no negative cost factor during the optimization.

Related

Gurobi dynamic probem

I am trying to make this kind of problem without add the equality constraint into the problem. This is useful to don't have equality constraints in my problem, get the standard form in matrix shape and apply other algorithm. This would be a simplification.
def main_building():
m = gp.Model("building")
m.reset() # Reset the problem, keep options.
PelHP = [m.addVar(name = 'PelHP_%d'%t) for t in range(T)] #Eletric Power
PthFC = [m.addVar(name = 'PthFC_%d'%t) for t in range(T)] #Thermal power given by the fan coil
T_ST_BMS= [m.addVar(name = 'T_ST_BMS_%d'%t) for t in range(T)] #Storage temperature
T_build_BMS= [m.addVar(name = 'T_build_BMS_%d'%t) for t in range(T)] #Building temperature
T_ST_BMS[0]=T_ST0+273.15
T_build_BMS[0]=T_BMS0+273.15
for t in range (T-1):
T_ST_BMS[t+1]=((COP_HP*PelHP[t]-PthFC[t]/eta_FC)/C_ST)*dt+T_ST_BMS[t]
for t in range(T-1):
m.addConstr(T_ST_BMS[t+1]<=273.15+60)
m.addConstr(-273.15+30 >= -T_ST_BMS[t+1] )
Objective_b=0
for t in range(T):
m.addConstr(PelHP[t]<=PelHP_max)
m.addConstr(PthFC[t]<=eta_FC*m_air*cp_air*(T_ST_BMS[t]-T_build_BMS[t]))
m.addConstr(0>=-PelHP[t])
m.addConstr(0>=-PthFC[t])
Objective_b=Objective_b+dt*(C_buy*(-(-PelHP[t]))+(T_build_BMS [t]-T_obj)**2
# Set objective:
m.setObjective(Objective_b, gp.GRB.MINIMIZE)
m.optimize()
return m
Gurobi is able to solve it with 10 times in T. But when I increase it, gurobi blocks. Anyone that could help me? The point is that the expression of T_ST_BMS becomes higher with t.

simulated annealing in python with multiple variables

I found this old stackoverflow article that essentially is exactly what I want.
Algorithm to optimize multiple variables more efficiently than trial-and-error
unforunately my more advanced maths are a bit lacking and I have some questions about the answer by ElKamina, if anyone can take a look and advise some of these basic math concepts, hopefully it will help me out.
The answer I am referring to is as follows:
def simAnneal( w, seed_x, numSteps=100000, sigma=0.01 ):
optimal_x = [i for i in seed_x]
optimal_w = w(optimal_x)
cur_w = w(seed_x)
for i in range(numSteps):
new_x = [i+random.gauss(0, sigma) for i in seed_x]
new_w = w(new_x)
if (new_w > cur_w) or (random.random() > new_w / cur_w) :
cur_x = new_x
cur_w = new_w
if cur_w > optimal_w:
optimal_w = cur_w
optimal_x = cur_x
return optimal_x
I am unfamiliar with seed_x, sigma and gaussian distribution so I am not sure how they are coming up with new_x.
I am attempting to solve a value based on many variables, (>10) and am trying to optimize better than randomly guessing as it would take forever.
Thanks!
Simulated Annealing TLDR:
We're trying to find a set of parameters that will maximize a function by adding random noise to parameters. If change leads to improvement, changes are accepted; once in a while we accept negative changes, but the probability of that lowers with time and how bad the change is.
In the snippet above, the function actually uses multiple parameters but accepts them as a list:
w is the function which parameters are optimized
seed_x is the initial guess of parameters - can be selected at random, but an informed guess would be better
Gaussian is just "shape" of the noise, such that small values are more common. random.random()*sigma (all values are equally likely) would work just fine there, too.
sigma is the magnitude of noise to be injected. It should not exceed a couple percent of typical param values. If param values vastly differ in magnitude, consider using a list of sigmas specific for each parameter.
MISSING: notion of temperature, which will actually make it simulated annealing
Rewriting it with temperature, more descriptive names, and more explicit:
def simAnneal(utility_func, initial_params, numSteps=100000,
noise_magnitude=0.01, cooling_rate=0.999):
optimal_params = initial_params
params = initial_params.copy() # lists are mutable, so .copy()
best_utility = utility = utility_func(*initial_params)
temperature = 1.0
for i in range(numSteps):
temperature *= cooling_rate
# consider using numpy/scipy for params and noise
new_params = [param+random.gauss(0, noise_magnitude)
for param in params]
# explicitly passing multiple parameters
new_utility = utility_func(*new_params)
if (new_utility > best_utility
or random.random()*temperature > new_utility / best_utility):
params, utility = new_params, new_utility
if new_utility > best_utility:
optimal_params, best_utility = params, utility
return optimal_params
Last but not least - unless the problem is extremely non-convex, I'd bet SGD would perform much better.

Pyomo - Objective function as average value of param

I am trying to use Pyomo for an LP problem and I would like the objective function to be the mean value of a particular parameter in my dataframe (which I'll call obj_param).
I had previously set this up like so:
model = ConcreteModel()
model.decision_var = Var(list(idx for idx in self.df.index), domain=NonNegativeReals)
model.obj = Objective(
expr= -1 * # because I want to maximize not minimize
sum(model.decision_var[idx] * df.loc[idx,'obj_param'] for idx in df.index)
)
The decision_var here is a column of counts (like "acres of this crop" in the classic farmer problem) and the obj_param is the value of this "crop", so my objective (as written) multiplies the acres of the crop by it's value to maximize the total value.
This makes sense in the farmer problem, but what I'm actually trying to do in my case is to maximize the mean value of each acre. (Forgive the farmer metaphor, it becomes a bit strained here.)
To do this, I change my objective as follows:
model.obj = Objective(
expr= -1 * # because I want to maximize not minimize
sum(model.decision_var[idx] * df.loc[idx,'obj_param'] for idx in df.index) /
sum(model.decision_var[idx] for idx in df.index)
)
Conceptually this looks right to me, but now when I run it I get RuntimeError: Cannot write legal LP file. Objective 'obj' has nonlinear terms that are not quadratic.
I can vaguely understand what this error is saying, but I don't totally see how this equation is non-linear. Either way, more generally I'm asking: is it possible in pyomo to define the objective as an average in the way that I'm trying to do?
Thanks for any help!

GAE: Why does GAE perform worse than normalized return and advantages

I'm implementing PPO with GAE as advantages. The following code is the way I compute GAE and returns according to OpenAI's baseline implementation.
advantages = np.zeros_like(rewards)
last_adv = 0
for i in reversed(range(len(rewards))):
delta = rewards[i] + nonterminals[i] * self._gamma * values[i+1] - values[i]
advantages[i] = last_adv = delta + nonterminals[i] * gamma * lam * last_adv
returns = advantages + values[:-1]
advantages = normalize(advantages) # normalize advantages
One thing worth to be noted is that values has one more element than other arrays like rewards so that values[-1] can be used as the extra next state. However, this implementation performs way worse than simply normalized return and advantages given below
returns = rewards
next_return = 0
for i in reversed(range(len(rewards))):
returns[i] = rewards[i] + nonterminals[i] * gamma * next_return
next_return = returns[i]
# normalize returns and advantages
values = normalize(values[:-1], np.mean(returns), np.std(returns))
advantages = normalize(returns - values)
returns = normalize(returns)
Without changing anything else, the above implementation constantly achieves the average score about 270+ in gym environment LunarLanderContinuous-v2. The GAE implementation, on the other hand, never achieve more than 100 score. See the following figure for an example, where the better one is run with the normalized implementation
What's wrong with my implementation?
In addition, here's the code for normalize
def normalize(x, mean=0., std=1., epsilon=1e-8):
x = (x - np.mean(x)) / (np.std(x) + epsilon)
x = x * std + mean
return x
Your code for computing the advantage seems correct. What does normalize do? Usually you standardize your data, meaning that you subtract its mean and divide by its standard deviation. I ask because in the second part of you code you pass the mean and the standard deviation of the return to the function normalize, while in the first portion you do not.
Also, why do you normalize values using returns in the second part of your code? It seems weird to me.
Finally, how do you train you V-function? (I assume that values contains the V-values). I found out that learning it as below
for epoch in range(epochs_v):
v_values = ... # compute your values using V
a_values = ... # compute A as in your code
target_values = v_values + a_values # generalized Bellman operator
# optimize V on your dataset with minibatches and ADAM
works better than "one-shot fit"
v_values = ... # compute your values using V
a_values = ... # compute A as in your code
target_values = v_values + a_values # generalized Bellman operator
# fit V to target_values

How to find the maximum of a prob in PuLP

I am trying to solve a linear problem in PuLP that minimizes a cost function. The cost function is itself a function of the maximum value of the cost function, e.g., I have a daily cost, and I am trying to minimize the monthly cost, which is the sum of the daily cost plus the maximum daily cost in the month. I don't think I'm capturing the maximum value of the function in the final solution, and I'm not sure how to go about troubleshooting this issue. The basic outline of the code is below:
# Initialize the problem to be solved
prob = LpProblem("monthly_cost", LpMinimize)
# The number of time steps
# price is a pre-existing array of variable prices
tmax = len(price)
# Time range
time = list(range(tmax))
# Price reduction at every time step
d = LpVariable.dict("d", (time), 0, 5)
# Price increase at every time step
c = LpVariable.dict("c", (time), 0, 5)
# Define revenues = price increase - price reduction + initial price
revenue = ([(c[t] - d[t] + price[t]) for t in time])
# Find maximum revenue
max_revenue = max(revenue)
# Initialize the problem
prob += sum([revenue[t]*0.0245 for t in time]) + max_revenue
# Solve the problem
prob.solve()
The variable max_revenue always equals c_0 - d_0 + price[0] even though price[0] is not the maximum of price and c_0 and d_0 both equal 0. Does anyone know how to ensure the dynamic maximum is being inserted into the problem? Thanks!
I don't think you can do the following in PuLP or any other standard LP solvers:
max_revenue = max(revenue)
This is because determining the maximum will require the solver to evaluate revenue equations; so in this case, I don't think you can extract a standard LP model. Such models are in fact non-smooth.
In such situations, you can easily reformulate the problem as follows:
max_revenue >= revenue = ([(c[t] - d[t] + price[t]) for t in time])
This works, as for any value of revenue: max_revenue >= revenue. This in turn helps in extracting a standard LP model from the equations. Hence, the original problem formulation gets extended with additional inequality constraints (the equality constraints and the objective functions should be the same as before). So it could look something like this (word of caution: I have not tested this):
# Define variable
max_revenue = LpVariable("Max Revenue", 0)
# Define other variables, revenues, etc.
# Add the inequality constraints
for item in revenue:
prob += max_revenue >= item
I would also suggest that you have a look at scipy.optimize.linprog. PuLP writes the model in an intermediary file, and then calls installed solver to solve the model. On the other hand, in scipy.optimize.linprog it's all done in python and should be faster. However, if your problem can not be solved using simplex algorithm, or you require other professional solvers (e.g. CPlex, Gurobi, etc.) then PuLP is a good choice.
Also, see the discussion on Data Fitting (page 19) in Introduction to Linear Optimisation by Bertsimas.
Hope this helps. Cheers.

Categories

Resources