Minimization model with initial starting values - python

I'm trying to solve a minimization problem where an initial solution is already present and the objective function is based on this initial solution.
I have some sort of line y_line which is an initial mapping of resources and stations:
y_line = np.array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
Additionally, I have a savings array for selling from the line S, an array for buying new EC and for processing P
S = np.array([[-260., -260., -260.],
[-30., -30., -30.],
[360., 360., 360.]], dtype=int)
EC = np.array([[1000, 1000, 1000],
[2000, 2000, 2000],
[5000, 5000, 5000]], dtype=int)
P = np.array([[720., 720., 720.],
[1440., 1440., 1440.],
[3600., 3600., 3600.]], dtype=int)
Using just a simplified constraint: every workstation i must have at least one resource j -> sum(y[i, j] for j in j_idx) == 1 for all i in i_idx.
My objective is that every sold resource from the initial y_line brings us savings, every newly bought costs us and the solution (the new line) y has a processing cost for operating. I have defined the objective as follows:
y_delta = y - y_line # delta between new line (y) and old line (y_line)
y_delta_plus = np.zeros(y.shape, dtype=object) # 1
y_delta_minus = np.zeros(y.shape, dtype=object) # 2
# I -> new bought resources
y_delta_plus[y_delta >= 0] = y_delta[y_delta >= 0]
# II -> sold resources
y_delta_minus[y_delta <= 0] = y_delta[y_delta <= 0]
c_i = y_delta_plus * EC # invest
c_s = y_delta_minus * S # savings
c_p = y * P # processing cost
c_y = np.sum(c_s + c_i + c_p)
However, if I solve this model (full code see below), then the objective value (5760) doesn't match my sanity check calculations (12430). Would it be possible to set initial values for y[i, j]? Or is there another function to achieve this?
from ortools.linear_solver import pywraplp
import numpy as np
y_line = np.array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
S = np.array([[-260., -260., -260.],
[-30., -30., -30.],
[360., 360., 360.]], dtype=int)
EC = np.array([[1000, 1000, 1000],
[2000, 2000, 2000],
[5000, 5000, 5000]], dtype=int)
P = np.array([[720., 720., 720.],
[1440., 1440., 1440.],
[3600., 3600., 3600.]], dtype=int)
solver = pywraplp.Solver('stack', pywraplp.Solver.SAT_INTEGER_PROGRAMMING)
y = np.zeros_like(y_line, dtype=object)
i_idx = range(y_line.shape[0])
j_idx = range(y_line.shape[1])
for i in i_idx:
for j in j_idx:
y[i, j] = solver.IntVar(0, 1, 'y[%i_%i]' % (i, j))
for i in i_idx:
solver.Add(
sum(y[i, j] for j in j_idx) == 1
)
def objective(y, y_line):
y_delta = y - y_line # delta between new line (y) and old line (y_line)
y_delta_plus = np.zeros(y.shape, dtype=object) # 1
y_delta_minus = np.zeros(y.shape, dtype=object) # 2
# I -> new bought resources
y_delta_plus[y_delta >= 0] = y_delta[y_delta >= 0]
# II -> sold resources
y_delta_minus[y_delta <= 0] = y_delta[y_delta <= 0]
c_i = y_delta_plus * EC # invest
c_s = y_delta_minus * S # savings
c_p = y * P # processing
return np.sum(c_s + c_i + c_p)
c_y = objective(y=y, y_line=y_line)
solver.Minimize(
c_y
)
# [START solve]
print("Number of constraints:", solver.NumConstraints())
print("Number of variables:", solver.NumVariables())
status = solver.Solve()
# [END solve]
y_new = np.zeros_like(y)
for i in range(y_line.shape[0]):
for j in range(y_line.shape[1]):
if y[i, j].solution_value() > 0:
y_new[i, j] = y[i, j].solution_value()
print(f"Objective sat: {solver.Objective().Value()}")
print(y_new)
# Number of constraints: 3
# Number of variables: 9
# Objective sat: 5760.0
# [[1.0 0 0]
# [1.0 0 0]
# [1.0 0 0]]
# %%
c_y_test = objective(y=y_new, y_line=y_line)
c_y_test # -> 12430.0

The model can be solved. However, not with the approach, I chose in the first place. Using a pywraplp model it didn't work, yet with a cp_model it can be solved using predefined variables (as mentioned by #sascha). The arrays y_line, S, EC and P are the same as above. The solemn constraint is the same as well. Yet, the "filtering" I could solve using:
for i in range(len(y_cp.flatten())):
model.AddElement(i, y_delta.flatten().tolist(), y_cp.flatten().tolist()[i] - y_line.flatten().tolist()[i])
for i in i_idx:
for j in j_idx:
model.AddMaxEquality(y_delta_plus[i, j], [y_delta[i, j], model.NewConstant(0)])
model.AddMinEquality(y_delta_minus[i, j], [y_delta[i, j], model.NewConstant(0)])
model.Minimize(
np.sum(y_delta_plus * EC) + np.sum(y_delta_minus * S) + np.sum(y_cp * P)
)
The solving and sanity check yields:
solver_cp = cp_model.CpSolver()
solver_cp.Solve(model)
y_new_cp = np.zeros_like(y_cp)
for i in i_idx:
for j in j_idx:
if solver_cp.Value(y_cp[i, j]) > 0:
y_new_cp[i, j] = solver_cp.Value(y_cp[i, j])
print(f"Objective cp: {solver_cp.ObjectiveValue()}")
print(y_new_cp)
# Objective cp: 5760.0
# [[1 0 0]
# [0 1 0]
# [1 0 0]]
c_y_test = objective(y=y_new_cp, y_line=y_line)
c_y_test # -> 5760 -> Correct
The cp_model could solve it and match the sanity check.
With the pywraplp model I couldn't figure out how to solve it.

Related

Value at a given index in a NumPy array depends on values at higher indexes in another NumPy array

I have two 1D NumPy arrays x = [x[0], x[1], ..., x[n-1]] and y = [y[0], y[1], ..., y[n-1]]. The array x is known, and I need to determine the values for array y. For every index in np.arange(n), the value of y[index] depends on x[index] and on x[index + 1: ]. My code is this:
import numpy as np
n = 5
q = 0.5
x = np.array([1, 2, 0, 1, 0])
y = np.empty(n, dtype=int)
for index in np.arange(n):
if (x[index] != 0) and (np.any(x[index + 1:] == 0)):
y[index] = np.random.choice([0,1], 1, p=(1-q, q))
else:
y[index] = 0
print(y)
The problem with the for loop is that the size of n in my experiment can become very large. Is there any vectorized way to do this?
Randomly generate the array y with the full shape.
Generate a bool array indicating where to set zeros.
Use np.where to set zeros.
Try this,
import numpy as np
n = 5
q = 0.5
x = np.array([1, 2, 0, 1, 0])
y = np.random.choice([0, 1], n, p=(1-q, q))
condition = (x != 0) & (x[::-1].cumprod() == 0)[::-1] # equivalent to the posted one
y = np.where(condition, y, 0)

ValueError: setting an array element with a sequence for generating a weighted data set?

This is the code I'm trying to run to generate a data set with 3 different sample populations, where one class is weighted by a combined Gaussian distribution with 2 sets of means and covariances -- hence the addition of the two multivariate normal rvs functions to feed into the indices of the 'blank' data set. Not sure what I can do to combine them without making it into a sequence?
N_valid = 10000
def generate_data_from_gmm(N, pdf_params, fig_ax=None):
# Determine dimensionality from mixture PDF parameters
n = pdf_params['mu'].shape[1]
print(n)
# Determine number of classes/mixture components
C = len(pdf_params['priors'])
# Output samples and labels
X = np.zeros([N, n])
labels = np.zeros(N)
# Decide randomly which samples will come from each component u_i ~ Uniform(0, 1) for i = 1, ..., N (or 0, ... , N-1 in code)
u = np.random.rand(N)
# Determine the thresholds based on the mixture weights/priors for the GMM, which need to sum up to 1
thresholds = np.cumsum(pdf_params['priors'])
thresholds = np.insert(thresholds, 0, 0) # For intervals of classes
marker_shapes = 'ox+*.' # Accomodates up to C=5
marker_colors = 'brgmy'
Y = np.array(range(1, C+1))
for y in Y:
# Get randomly sampled indices for this component
indices = np.argwhere((thresholds[y-1] <= u) & (u <= thresholds[y]))[:, 0]
# No. of samples in this component
Ny = len(indices)
labels[indices] = y * np.ones(Ny) - 1
if n == 1:
X[indices, 0] = norm.rvs(pdf_params['mu'][y-1], pdf_params['Sigma'][y-1], Ny)
else:
X[indices, :] = (multivariate_normal.rvs(pdf_params['mu'][y-1], pdf_params['Sigma'][y-1], Ny) + multivariate_normal.rvs(pdf_params['mu'][y], pdf_params['Sigma'][y], Ny))
gmm_pdf = {}
# Likelihood of each distribution to be selected AND class priors!!!
gmm_pdf['priors'] = np.array([0.65, 0.35])
gmm_pdf['mu'] = np.array([[3, 0],
[0, 3],
[2, 2]]) # Gaussian distributions means
gmm_pdf['Sigma'] = np.array([[[2, 0],
[0, 1]],
[[1, 0],
[0, 2]],
[1,0],
[0,1]]) # Gaussian distributions covariance matrices
This specifically happens in this line:
X[indices, :] = (multivariate_normal.rvs(pdf_params['mu'][y-1], pdf_params['Sigma'][y-1], Ny)
+ multivariate_normal.rvs(pdf_params['mu'][y], pdf_params['Sigma'][y], Ny))
Any ideas?

Find max distance from (0,0) and add to legend matplotlib

I have this code calculating a random walk that I am trying to find the max distance from (0.0) for all walks and add them to a legend. Added an image of the result I want to achieve.
import numpy as np
import matplotlib.pyplot as plt
import math
np.random.seed(12)
repeats = 5
N_steps = 1000000
expected_R = np.sqrt(N_steps)
plt.title(f"{repeats} random walks of {N_steps} steps")
for x in range(repeats):
dirs = np.random.randint(0, 4, N_steps)
steps = np.empty((N_steps, 2))
steps[dirs == 0] = [0, 1] # 0 - right
steps[dirs == 1] = [0, -1] # 1 - left
steps[dirs == 2] = [1, 0] # 2 - up
steps[dirs == 3] = [-1, 0] # 3 - down
steps = steps.cumsum(axis=0)
print("Final position:", steps[-1])
skip = N_steps // 5000 + 1
xs = steps[::skip, 0]
ys = steps[::skip, 1]
x = max(ys)
plt.plot(xs, ys)
circle = plt.Circle((0, 0), radius=expected_R, color="k")
plt.gcf().gca().add_artist(circle)
plt.gcf().gca().set_aspect("equal")
plt.axis([-1500-x,1500+x,-1500-x,1500+x])
plt.show()
You can plot the distance from the coordinates steps to 0,0 by using distance=np.linalg.norm(steps, axis=1). And you can then take the max of this array to find the maximum distance. You can then add a label to your plots and a legend.
See code below:
import numpy as np
import matplotlib.pyplot as plt
import math
np.random.seed(12)
repeats = 5
N_steps = 1000000
expected_R = np.sqrt(N_steps)
plt.title(f"{repeats} random walks of {N_steps} steps")
max_distance=np.zeros(repeats)
for x in range(repeats):
dirs = np.random.randint(0, 4, N_steps)
steps = np.empty((N_steps, 2))
steps[dirs == 0] = [0, 1] # 0 - right
steps[dirs == 1] = [0, -1] # 1 - left
steps[dirs == 2] = [1, 0] # 2 - up
steps[dirs == 3] = [-1, 0] # 3 - down
steps = steps.cumsum(axis=0)
print("Final position:", steps[-1])
skip = N_steps // 5000 + 1
xs = steps[::skip, 0]
ys = steps[::skip, 1]
distance=np.linalg.norm(steps, axis=1)
max_distance[x]=np.amax(distance)
plt.plot(xs, ys,label='Random walk '+str(x)+': max distance: '+str(np.round(max_distance[x],1)))
circle = plt.Circle((0, 0), radius=expected_R, color="k")
plt.gcf().gca().add_artist(circle)
plt.gcf().gca().set_aspect("equal")
plt.axis([-1500-x,1500+x,-1500-x,1500+x])
plt.legend(fontsize=8)
plt.show()
And the output gives:

Calculating rational basis for the nullspace using numpy

I am trying to calculate the rational basis for null space of a matrix. There is quite a few posts about how nullspace is calculated using Python/numpy but they calculate it for orthonormal basis and not for the rational basis. Here is how this is done in MATLAB:
ns = null(A,'r')
When I look at the source code, I saw that it is calculated like this:
function Z = null(A,how)
[m,n] = size(A)
%...
[R,pivcol] = rref(A);
r = length(pivcol);
nopiv = 1:n;
nopiv(pivcol) = [];
Z = zeros(n,n-r,class(A));
if n > r
Z(nopiv,:) = eye(n-r,n-r,class(A));
if r > 0
Z(pivcol,:) = -R(1:r,nopiv);
end
end
%...
function [A,jb] = rref(A,tol)
%...
[m,n] = size(A);
[num, den] = rat(A);
rats = isequal(A,num./den);
if (nargin < 2), tol = max(m,n)*eps(class(A))*norm(A,'inf'); end
i = 1;
j = 1;
jb = [];
while (i <= m) && (j <= n)
[p,k] = max(abs(A(i:m,j))); k = k+i-1;
if (p <= tol)
A(i:m,j) = zeros(m-i+1,1);
j = j + 1;
else
jb = [jb j];
A([i k],j:n) = A([k i],j:n);
A(i,j:n) = A(i,j:n)/A(i,j);
for k = [1:i-1 i+1:m]
A(k,j:n) = A(k,j:n) - A(k,j)*A(i,j:n);
end
i = i + 1;
j = j + 1;
end
end
if rats
[num,den] = rat(A);
A=num./den;
end
Here rref is the reduced row echelon form. Thus by looking at this source code I tried to recreate it with following code:
def fract(x):
return Fraction(x)
def dnm(x):
return x.denominator
def nmr(x):
return x.numerator
fractionize = np.vectorize(fract)
denom = np.vectorize(dnm)
numer = np.vectorize(nmr)
def rref(A,tol=1e-12):
m,n = A.shape
Ar = A.copy()
i,j = 0,0
jb = []
while i < m and j < n:
p = np.max(np.abs(Ar[i:m,j]))
k = np.where(np.abs(Ar[i:m,j]) == p)[0][0]
k = k + i - 1
if (p <= tol):
Ar[i:m,j] = np.zeros((m-i,))
j += 1
else:
jb.append(j)
Ar[(i,k),j:n] = Ar[(k,i),j:n]
Ar[i,j:n] = Ar[i,j:n]/Ar[i,j]
for k in np.hstack((np.arange(0,i),np.arange(i+1,m))):
Ar[k,j:n] = Ar[k,j:n] - Ar[k,j]*A[i,j:n]
i += 1
j += 1
print(len(jb))
return Ar,jb
def null(A,tol=1e-5):
m,n = A.shape
R,pivcol = rref(A,tol=tol)
print(pivcol)
r = len(pivcol)
nopiv = np.ones(n).astype(bool)
nopiv[pivcol] = np.zeros(r).astype(bool)
Z = np.zeros((n,n-r))
if n > r:
Z[nopiv,:] = np.eye(n-r,n-r)
if r > 0:
Z[pivcol,:] = -R[:r,nopiv]
return Z
There are two things that I don't know. First, I do not know how to add the ratios part into rref function. Second, I am not sure if my indexes are correct since MATLAB's indices are start from 1 and indexing includes the last element when you choose for a slice (i.e. 1:5 includes both 1 and 5).
SymPy does that out of the box, although (being symbolic, and in Python) not as fast as NumPy or Scipy would. An example with floating point input:
from sympy import Matrix, S, nsimplify
M = Matrix([[2.75, -1.2, 0, 3.2], [8.29, -4.8, 7, 0.01]])
print(nsimplify(M, rational=True).nullspace())
Prints a list of two column vectors, represented as one-column matrices.
[Matrix([
[ 700/271],
[9625/1626],
[ 1],
[ 0]]), Matrix([
[ -1279/271],
[-17667/2168],
[ 0],
[ 1]])]
The use of nsimplify was necessary to convert floats to the rationals that they were meant to represent. If the matrix is created as a matrix of integer/rational entries, that would not be necessary.
M = Matrix([[1, 2, 3, 5, 9], [9, -3, 0, 2, 4], [S(3)/2, 0, -1, 2, 0]])
print(M.nullspace())
[Matrix([
[ -74/69],
[-176/69],
[ 9/23],
[ 1],
[ 0]]), Matrix([
[ -70/69],
[-118/69],
[ -35/23],
[ 0],
[ 1]])]
Here, S(3)/2 is used instead of `3/2 in order to force SymPy object creation instead of floating point evaluation.

Numpy submatrix(selected random index) calculation performance

I tried to calculate the sub-matrix using Numpy.
The shape of matrices are
A : (15000, 100)
B : (15000, 100)
B_ : (3000, 100)
C : (100, 100)
sample_index = np.random.choice(np.arange(int(15000*0.2)), size=int(int(15000*0.2)), replace=False)
and the first code is
for ki in range(100):
self.A[sample_index, k] += B_[:, k] - np.dot(self.A[sample_index, : ], C[:, k])
which only use sub matrix sliced from sample_index
and the second code is
for k in range(100):
self.A[:, k] += B[:, k] - np.dot(self.A[:, : ], C[:, k])
which use all matrix.
But the calculation time of first code is slower than second code.
Do you know any reason or any solutions to speed-up?
You are actually copying the input matrix. If you are just reading the input, you don't have to copy it.
import numpy as np
a = np.random.rand(10000).reshape(100, 100)
b = np.random.rand(10000).reshape(100, 100)
i = list(range(10))
a_sub0 = a[:10] # view
a_sub1 = a[i] # copying
# you can change the original matrix from the view
a_sub0[0, 0] = 100
(a[0, 0] == 100.0) and (a_sub1[0, 0] != 100.0) # True

Categories

Resources