1

I have 2 arrays of n elements each. The first is a percentage (ranging from 0% to 100%) where the sum of all elements yields 100%. The second is constituted of integers ranging from -3 to 3 (inclusive).

The objective of my script is to vary the numbers just enough to reach a sum product of near 0.

Here is what I have so far and seems to be working:

import numpy as np
import cvxpy as cp

# Emulated user inputs
n = 10
arr1 = np.random.rand(n)
arr1 = arr1/np.sum(arr1)
arr2 = np.random.randint(-3, 3, n)
while not (-0.1 < np.sum(np.dot(arr1, arr2)) < 0.1):  # User ensures this condition before running
    arr2 = np.random.randint(-3, 3, n)

print("Initial array       =", arr2)
print("Initial sum product =", np.sum(np.dot(arr1, arr2)), "\n")


# Gradually increase allowable element-wise delta
for delta_limit in range(2, 7):  # Variation of 2 is acceptable for first iteration
    # Create variable for new array
    arr3 = cp.Variable(len(arr2), integer=True)

    # Define constraints
    constraints = [-0.000050001 <= arr3 * arr1,  # Not working, but it's supposed to target sum product ~0
                   arr3 * arr1 <= 0.000049999,   # Not working, but it's supposed to target sum product ~0
                   -3 <= arr3,                   # Lower bound of -3
                   arr3 <= 3,                    # Upper bound of +3
                   -delta_limit <= arr2 - arr3,  # Restrict element-wise difference by less than +/-delta_limit
                   arr2 - arr3 <= delta_limit]   # Restrict element-wise difference by less than +/-delta_limit

    # Objective function (minimize variation between arr3 and arr2)
    objective = cp.Minimize(cp.multiply((1 / (len(arr2) - 1)), cp.sum(cp.power((arr3 - arr2) - arr2.mean(), 2))))

    # Solve
    solution = cp.Problem(objective, constraints).solve()

    # Check if a solution was found
    if arr3.value is None and delta_limit < 6:
        continue
    elif arr3.value is None and delta_limit == 6:
        print("No solution found, try another combination")
    else:
        # Change float to integer
        arr3.value = arr3.value.astype(int)

        # Print results
        print("New array           =", arr3.value)
        print("New sum product     =", np.sum(np.dot(arr1, arr3.value)), "\n")
        print("Differences         =", arr3.value - arr2)
        print("Total difference    =", np.round(np.sum(np.absolute(arr3.value - arr2))))
        break

This will, for example, print the following:

Initial array       = [ 1  1  1 -2  2  2 -2  0 -2 -2]
Initial sum product = -0.05254026276509359 

New array           = [ 0  0  0 -2  1  1 -2  0 -1 -2]
New sum product     = -0.3703945724305102 

Differences         = [-1 -1 -1  0 -1 -1  0  0  1  0]
Total difference    = 6

However, I would like to add 1 more constraint: "the sign of the elements in the first array should be maintained in the new array". I would preferably do this as a constraint and not just a for loop check.

I have tried stuff like this without success:

a) np.sign(arr2) == np.sign(arr3)
b) np.sign(arr2) == cp.multiply(arr3 + 0.1, cp.power(cp.power(arr3 + 0.1, 2, -0.5))

And many more, I just can't crack it... :( Also, if you have suggestions for improvements, I am very open to those.

Thanks in advance!

1 Answers1

1

I propose you the following code.

import statistics as st
import math

array = [ 1, 1, 1, -2, 2, 2, -2, 0, -2, -2]
new = []


def getSearchRange(e):
    if e > 0:
        search_in = range(0, 3)
    elif e < 0:
        search_in = range(-3, 0)
    else:
        search_in = range(-3, 3)
    # ...
    return search_in

def sum_check(new, search_in):
    result = []
    store_num =[]
    for n in search_in:
        if new:
            result.append(math.fabs(st.mean(new) + n/len(new)))
        else:
            result.append(math.fabs(n))
        store_num.append(n)
    index = result.index(min(result))
    # ...
    return store_num[index]


for e in array:
    search_in = getSearchRange(e)
    new.append(sum_check(new, search_in))

print(new)   

it gives me a solution that seems to fulfill what you are looking for ? :

[0, 0, 0, -1, 1, 0, -1, 1, -1, -1] (average 0.2)

Note: if you really want to vary number in the output you could use a randomint function to inject 2 or 3 different numbers (n) during the convergence process. But the optimal solution seems to be the one the script gives.

import random
...
store_num.append(random.randint(-3, 3)) # 2 or 3 times```
Laurent B.
  • 1,653
  • 1
  • 7
  • 16
  • I think that your suggestion is working, as I intended, however its missing the sum product, I'm not sure how this is supposed to be implemented, any suggestions? Namely: -0.00005001 < np.sum(np.dot(arr1, new)) < 0.00004999 – Alexis Andruskiewitsch May 08 '20 at 13:28
  • hi, try with: print("New sum product =", np.sum(np.dot(array, new)), "\n"). I found sum is 10 – Laurent B. May 08 '20 at 13:44