0

I need to use a multiprocessing technique in a class that call a method of another class. Below the explanation.

I am coding a multi-agent strategy. So, my code is structured in 3 classes:

  1. agent class
  2. environment class
  3. main.

In the agent class, I am running a MILP problem that return a dictionary of decision variables:

class Agent:
    def __init__():
        ....
    def Optimisation():
       .....
       return decision_agent

The class environment receives the output from all the agents to compute the 'community' output. Since every agent is an independent entity, agents will be computing their optimisation problem in parallel. Therefore, I want to use multiprocessing in the environment class to get the decisions of all agents

import Agent as Agent

class Env:
    def __init__():
         self.agents = #list of agents in my environment

    def community_output():
        #**start the part of code I want to multiprocess**
        for agent in self.agents:
            agents_output.append(agent.Optimisation) 
        #**end the part of code I want to multiprocess**

        """compute community_output based on agents_output """

        return community_output

Then in the main, call the method community_output():

import Env as Env

if __name__ == '__main__': 
    env = Env()
    result = env.community_output()

How can I proceed please?

Since I have a list of instance method as an iterable, I tried the following:

def job(instance):
    instance.Optimisation()
    return instance

if __name__ == '__main__':
   pool = mp.Pool(processes=4)
   agents_output= pool.map(job, agents)
   pool.close() 
   pool.join()

This only works if I remove the Env class and put all its processing before def job(instance).

When I integrate it in the Env class:

import Agent as Agent

class Env:
    def __init__():
         self.agents = #list of agents in my environment

    def job(instance):
        instance.Optimisation()
        return instance

    def community_output():
        pool = mp.Pool(processes=4)
        agents_output = pool.map(self.job, self.agents)
        pool.close() 
        pool.join()

        """ Compute community_output based on agents_output """

        return community_output
Import Env as Env
if __name__ == '__main__': 
    env = Env()
    result = env.community_output()

I got the error:

  File "C:\Users\chaim\...\Env.py", line 576, in community_output
    results = pool.map(self.job, self.agents)
  File "C:\Users\chaim\anaconda3\envs\picos\lib\multiprocessing\pool.py", line 268, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "C:\Users\chaim\anaconda3\envs\picos\lib\multiprocessing\pool.py", line 657, in get
    raise self._value
multiprocessing.pool.MaybeEncodingError: Error sending result: '[{'opt_prob': <Mixed-Integer Linear Program>, 'q_us_val': <190x1 matrix, tc='d'>, 'q_ds_val': 0.0, 'p_net_exp_val': <48x1 matrix, tc='d'>, 'q_sup_sell_val': <48x1 matrix, tc='d'>, 'q_sup_buy_val': <48x1 matrix, tc='d'>, 'p_es_ch_val': <48x1 matrix, tc='d'>, 'p_es_dis_val': <48x1 matrix, tc='d'>, 'q_ds_0': <1×1 Real Constant: 0>, 'q_us_0': <1×1 Real Constant: q_usᵀ·[190×1]>}]'. Reason: 'PicklingError("Can't pickle <function <lambda> at 0x000001B8B9C31288>: attribute lookup <lambda> on picos.modeling.options failed")'

I note that:

{'opt_prob': <Mixed-Integer Linear Program>, '... q_usᵀ·[190×1]>}

is the dictionary returned by the method agent.Optimisation and picos is the optimisation API used.

Any clue how can I solve this?

martineau
  • 119,623
  • 25
  • 170
  • 301
Chaimaa
  • 1
  • 3
  • Class methods all automatically receive an initial argument — usually named `self` — which is the instance of the class they are part of. You don't have that in almost all of your code. Please provide a runnable [mre] that others can use to test their answers with. – martineau Jun 25 '21 at 00:15
  • 1
    Yes, I have the 'self' included in all my class methods. I will try to elaborate a minimal reproducible example to avoid confusions. – Chaimaa Jun 25 '21 at 09:49

2 Answers2

0

are you trying to call Optimisation() from the Agent Module? If So then shouldn't

def job(instance):
    instance.Optimisation()
    return instance

instead be

def job():
    return Agent.Optimisation()
Pythonics
  • 122
  • 3
  • 1
    Actually, I am trying to call Optimisation from Env Module. The complexity is that I want to call Optimisation() from Env using multiprocessing. – Chaimaa Jun 25 '21 at 09:32
0

I'm not familiar with the PICOS per se, but after making a few assumptions about it I think the following is a general outline of how you could do what you want. Note that the function arument being passed to pool.map() is the unbound Agent class method optimisation(). It will be called with one of the Agent instances you have in the list of them.

import multiprocessing as mp
from random import randrange  # For testing purposes.


class Agent:
    def __init__(self):
        ...
    def optimisation(self):
       decision_agent = {'var1': randrange(10), 'var2': randrange(10), 'var3': randrange(10)}
       return decision_agent


class Env:
    def __init__(self, num_agents):
         self.agents = [Agent() for _ in range(num_agents)]

    def community_output(self):
        with mp.Pool(processes=4, maxtasksperchild=1) as pool:
            agents_output = pool.map(Agent.optimisation, self.agents)
        return agents_output


if __name__ == '__main__':
    env = Env(3)
    result = env.community_output()
    print(result)
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thank you for the response. The suggestion still produce the same error. I tried to elaborate a minimal reproducible example. But surprisingly, it worked fine! I think I have a problem in the structure of my classes. – Chaimaa Jun 25 '21 at 17:13
  • The error comes from the fact that I was returning in the dictionary the optimisation problem itself {'opt_prob': is the instance of picos.Problem()}. Now, it returns an exception – Chaimaa Jun 25 '21 at 18:03
  • `Exception in thread Thread-4: Traceback (most recent call last): File "C:\Users\chaim\anaconda3\envs\tf\lib\threading.py", line 932, in _bootstrap_inner self.run() File "C:\Users\chaim\anaconda3\envs\tf\lib\threading.py", line 870, in run self._target(*self._args, **self._kwargs) File "C:\Users\chaim\anaconda3\envs\tf\lib\multiprocessing\pool.py", line 576, in _handle_results task = get()` – Chaimaa Jun 25 '21 at 18:04
  • `File "C:\Users\chaim\anaconda3\envs\tf\lib\multiprocessing\connection.py", line 251, in recv return _ForkingPickler.loads(buf.getbuffer()) TypeError: __new__() missing 2 required positional arguments: 'glyph' and 'operands'` – Chaimaa Jun 25 '21 at 18:04
  • When you pass arguments to or return values from processes in Python, they must be "pickleable" — it looks like the dictionaries being returned contain values that cannot be. – martineau Jun 25 '21 at 18:42