3

I'm solving a multi objective optimization problem with pymoo and integrated with Dask. In pymoo you have to define a Problem class to pass it to the optimizer, I started with this class

class ChungProblemWeightedDask(ChungProblemDask):
    def __init__(self, client: Client, weights: np.ndarray, *args, **kwargs):
        super().__init__(client, *args, **kwargs)

        self.difference_weights_matrix = np.abs(
            np.subtract.outer(weights, weights)
        )

        self.client = client

    def _evaluate(self, X, out, *args, **kwargs):

        W = X[:, 0]

        X = X[:, 1:]

        N = self.occurrence_matrix

        P = self.difference_weights_matrix

        def F13(ind, H):
            return -sum(np.dot(var, np.matmul(H, var)) for var in ind)

        X_pop = [self.list_to_matrix(ind, self.n_skus) for ind in X]

        jobs = [
            *self.client.map(F13, X_pop, H=N),
            *self.client.map(F13, X_pop, H=-P),
        ]

        jobs_results = self.client.gather(jobs)

        out["F"] = np.column_stack(
            [
                np.row_stack(jobs_results[0 : len(X_pop)]),
                np.row_stack(jobs_results[len(X_pop) :]),
                W,
            ]
        )

        out["G"] = self.constraints(X_pop, W)


Here the client is a dask.distributed.Client and could be a LocalCluster or distributed across multiples machines (for development I'm using a local one). The class ChungProblemDask is a class where I simply define all the logic of my optimization problem, and I think it's not important for the question I have. The function F13 inside (why inside? look question 2) the _evaluate method is the function to optimmize, and look that I use twice, for the matrix N and P (very large matrices).

First Question

When I solve the optimization problem with the class defined above I have no problems and everything works fine. But look close, the way that I'm solving this, in my opinion, is not optimal because I have to map twice the variable X_pop when I do
jobs = [*self.client.map(F13, X_pop, H=N),*self.client.map(F13, X_pop, H=P)] , being that the function is the same with different matrices only. I tried something like this

def F13(ind):
    F1 = 0
    F3 = 0
    for var in ind:
        F1 = F1 + np.dot(var, np.matmul(N, var))
        F3 = F3 + np.dot(var, np.matmul(P, var))

    return -F1, F3

X_pop = [self.list_to_matrix(ind, self.n_skus) for ind in X]

jobs = self.client.map(F13, X_pop)

jobs_results = self.client.gather(jobs)

   

When I ran this I threw the error concurrent.futures._base.CancelledError, but impressively if I change the function F13 to this form

def F13(ind):
    F1 = 0
    F3 = 0
    for var in ind:
        F1 = F1 + np.dot(var, np.matmul(N, var))
        F3 = F3 + np.dot(var, np.matmul(N, var))

    return -F1, F3

i.e F1 and F3 are the same (note that I use N twice), I have not problem and everything works fine!. What's going on here? It has to do with the fact of returning a tuple?

Second Question

I can't move the definition of F13 to a staticmethod and get it out from inside the _evaluate method, I got PickleError. I know that the way to distribute the data in the workers goes through a serialization process.

There is a way to do something like this?

class ChungProblemWeightedDask(ChungProblemDask):
    def __init__(self, client: Client, weights: np.ndarray, *args, **kwargs):
        super().__init__(client, *args, **kwargs)

        self.difference_weights_matrix = np.abs(
            np.subtract.outer(weights, weights)
        )

        self.client = client

    @staticmethod
    def F13(ind, H):
        return -sum(np.dot(var, np.matmul(H, var)) for var in ind)

    def _evaluate(self, X, out, *args, **kwargs):

        W = X[:, 0]

        X = X[:, 1:]

        N = self.occurrence_matrix

        P = self.difference_weights_matrix

        X_pop = [self.list_to_matrix(ind, self.n_skus) for ind in X]

        jobs = [
            *self.client.map(self.F13, X_pop, H=N),
            *self.client.map(self.F13, X_pop, H=P),
        ]

        jobs_results = self.client.gather(jobs)

        out["F"] = np.column_stack(
            [
                np.row_stack(jobs_results[0 : len(X_pop)]),
                np.row_stack(jobs_results[len(X_pop) :]),
                W,
            ]
        )

        out["G"] = self.constraints(X_pop, W)


PS This is my first time using dask, any comments that help with optimization are welcome. If something I have written does not make sense, I apologize. If additional information is needed I can provide it.

PS2 The algorithm That I use is the NSGAII. The matrices in the game are huge (4000x4000 app). We use app 15000 iterations and in every iteration we have to calculate app 200 matrix multiplications. Doing this without dask tooks 8 days, and with 4-5 hrs. Running in a single but powerful machine.

Andrex
  • 602
  • 1
  • 7
  • 22

0 Answers0