Comunicación entre dos motores Python separados.

La statement del problema es la siguiente:

Estoy trabajando con Abaqus, un progtwig para analizar problemas mecánicos. Básicamente es un intérprete de Python independiente con sus propios objetos, etc. Dentro de este progtwig, ejecuto un script de Python para configurar mi análisis (por lo que este script puede modificarse). También contiene un método que debe ejecutarse cuando se recibe una señal externa. Estas señales provienen del script principal que estoy ejecutando en mi propio motor Python.

Por ahora, tengo el siguiente flujo de trabajo: la secuencia de comandos principal establece un valor booleano en Verdadero cuando la secuencia de comandos de Abaqus tiene que ejecutar una función específica, y encoge este valor booleano en un archivo. La secuencia de comandos Abaqus comprueba regularmente este archivo para ver si el valor booleano se ha establecido en verdadero. Si es así, realiza un análisis y declina la salida, de modo que el script principal pueda leer esta salida y actuar sobre ella.

Estoy buscando una manera más eficiente de señalar el otro proceso para iniciar el análisis, ya que hay muchas comprobaciones innecesarias que se están realizando. El intercambio de datos a través de pickle no es un problema para mí, pero una solución más eficiente es ciertamente bienvenida.

Los resultados de búsqueda siempre me dan soluciones con subproceso o similares, que son para dos procesos iniciados dentro del mismo intérprete. También he analizado ZeroMQ, ya que se supone que esto debe lograr cosas como esta, pero creo que esto es excesivo y me gustaría una solución en Python. Ambos intérpretes están ejecutando python 2.7 (aunque versiones diferentes)

Editar:

Como @MattP, agregaré esta statement de mi entendimiento:

Fondo

Creo que estás ejecutando un producto llamado abaqus. El producto abaqus incluye un intérprete de python vinculado al que puede acceder de alguna manera (posiblemente ejecutando abaqus python foo.py en la línea de comandos).

También tiene una instalación de python separada, en la misma máquina. Está desarrollando un código, posiblemente incluyendo numpy / scipy, para ejecutarlo en esa instalación de Python.

Estas dos instalaciones son diferentes: tienen diferentes intérpretes binarios, diferentes bibliotecas, diferentes rutas de instalación, etc. Pero viven en el mismo host físico.

Su objective es habilitar los progtwigs “plain python”, escritos por usted, para comunicarse con uno o más scripts que se ejecutan en el entorno de “Abaqus python”, de modo que dichos scripts puedan realizar trabajos dentro del sistema Abaqus y obtener resultados.

Solución

Aquí hay una solución basada en socket. Hay dos partes, abqlistener.py y abqclient.py . Este enfoque tiene la ventaja de que utiliza un mecanismo bien definido para “esperar al trabajo”. No sondeo de archivos, etc. Y es una API “dura”. Puede conectarse a un proceso de escucha desde un proceso en la misma máquina, ejecutando la misma versión de python, o desde una máquina diferente, o desde una versión diferente de python, o desde ruby ​​o C o perl o incluso COBOL. Le permite poner un “espacio de air” real en su sistema, para que pueda desarrollar las dos partes con un acoplamiento mínimo.

La parte del servidor es abqlistener . La intención es copiar algo de este código en su secuencia de comandos de Abaqus. El proceso abq se convertiría en un servidor, escuchando las conexiones en un número de puerto específico y haciendo el trabajo en respuesta. Enviando una respuesta, o no. Etcétera.

No estoy seguro de si necesita realizar un trabajo de configuración para cada trabajo. Si es así, eso tendría que ser parte de la conexión. Esto solo iniciaría ABQ, escucharía en un puerto (para siempre) y atendería las solicitudes. Cualquier configuración específica del trabajo tendría que ser parte del proceso de trabajo. (Tal vez envíe una cadena de parámetros, o el nombre de un archivo de configuración, o lo que sea).

La parte del cliente es abqclient . Esto podría ser movido a un módulo, o simplemente copiar / pegar en su código de progtwig no ABQ existente. Básicamente, abre una conexión con el host correcto: combinación de puertos, y está hablando con el servidor. Enviar algunos datos, recuperar algunos datos, etc.

Este material se raspa principalmente del código de ejemplo en línea. Por lo tanto, debería parecer muy familiar si empiezas a profundizar en algo.

Aquí está abqlistener.py:

 # The below usage example is completely bogus. I don't have abaqus, so # I'm just running python2.7 abqlistener.py [options] usage = """ abacus python abqlistener.py [--host 127.0.0.1 | --host mypc.example.com ] \\ [ --port 2525 ] Sets up a socket listener on the host interface specified (default: all interfaces), on the given port number (default: 2525). When a connection is made to the socket, begins processing data. """ import argparse parser = argparse.ArgumentParser(description='Abacus listener', add_help=True, usage=usage) parser.add_argument('-H', '--host', metavar='INTERFACE', default='', help='Interface IP address or name, or (default: empty string)') parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525, help='port number of listener (default: 2525)') args = parser.parse_args() import SocketServer import json class AbqRequestHandler(SocketServer.BaseRequestHandler): """Request handler for our socket server. This class is instantiated whenever a new connection is made, and must override `handle(self)` in order to handle communicating with the client. """ def do_work(self, data): "Do some work here. Call abaqus, whatever." print "DO_WORK: Doing work with data!" print data return { 'desc': 'low-precision natural constants','pi': 3, 'e': 3 } def handle(self): # Allow the client to send a 1kb message (file path?) self.data = self.request.recv(1024).strip() print "SERVER: {} wrote:".format(self.client_address[0]) print self.data result = self.do_work(self.data) self.response = json.dumps(result) print "SERVER: response to {}:".format(self.client_address[0]) print self.response self.request.sendall(self.response) if __name__ == '__main__': print args server = SocketServer.TCPServer((args.host, args.port), AbqRequestHandler) print "Server starting. Press Ctrl+C to interrupt..." server.serve_forever() 

Y aquí está abqclient.py :

 usage = """ python2.7 abqclient.py [--host HOST] [--port PORT] Connect to abqlistener on HOST:PORT, send a message, wait for reply. """ import argparse parser = argparse.ArgumentParser(description='Abacus listener', add_help=True, usage=usage) parser.add_argument('-H', '--host', metavar='INTERFACE', default='', help='Interface IP address or name, or (default: empty string)') parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525, help='port number of listener (default: 2525)') args = parser.parse_args() import json import socket message = "I get all the best code from stackoverflow!" print "CLIENT: Creating socket..." s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "CLIENT: Connecting to {}:{}.".format(args.host, args.port) s.connect((args.host, args.port)) print "CLIENT: Sending message:", message s.send(message) print "CLIENT: Waiting for reply..." data = s.recv(1024) print "CLIENT: Got response:" print json.loads(data) print "CLIENT: Closing socket..." s.close() 

Y aquí está lo que imprimen cuando los ejecuto juntos:

 $ python2.7 abqlistener.py --port 3434 & [2] 44088 $ Namespace(host='', port=3434) Server starting. Press Ctrl+C to interrupt... $ python2.7 abqclient.py --port 3434 CLIENT: Creating socket... CLIENT: Connecting to :3434. CLIENT: Sending message: I get all the best code from stackoverflow! CLIENT: Waiting for reply... SERVER: 127.0.0.1 wrote: I get all the best code from stackoverflow! DO_WORK: Doing work with data! I get all the best code from stackoverflow! SERVER: response to 127.0.0.1: {"pi": 3, "e": 3, "desc": "low-precision natural constants"} CLIENT: Got response: {u'pi': 3, u'e': 3, u'desc': u'low-precision natural constants'} CLIENT: Closing socket... 

Referencias:

argparse , SocketServer , json , socket son todas las bibliotecas de Python “estándar”.

Para ser claros, tengo entendido que está ejecutando Abaqus / CAE a través de un script Python como un proceso independiente (llamémoslo abq.py ), que verifica, abre y lee un archivo desencadenante para determinar si debería ejecutar un análisis. . El archivo disparador es creado por un segundo proceso de Python (llamémoslo main.py ). Finalmente, main.py espera a leer el archivo de salida creado por abq.py Desea una forma más eficiente de indicar a abq.py que ejecute un análisis, y está abierto a diferentes técnicas para intercambiar datos.

Como mencionó, el subproceso o multiprocesamiento podría ser una opción. Sin embargo, creo que una solución más simple es combinar sus dos scripts y, opcionalmente, usar una función de callback para monitorear la solución y procesar su salida. abq.py no es necesario que abq.py se abq.py constantemente como un proceso separado, y que todos los análisis se pueden iniciar desde main.py siempre que sea apropiado.

Deje que main.py tenga acceso a Abaqus Mdb. Si ya está construido, lo abres con:

 mdb = openMdb(FileName) 

No se necesita un archivo de activación si main.py inicia todos los análisis. Por ejemplo:

 if SomeCondition: j = mdb.Job(name=MyJobName, model=MyModelName) j.submit() j.waitForCompletion() 

Una vez completado, main.py puede leer el archivo de salida y continuar. Esto es sencillo si el archivo de datos fue generado por el propio análisis (por ejemplo, archivos .dat o .odb ). OTH, si el archivo de salida es generado por algún código en su abq.py actual, entonces probablemente solo puede incluirlo en main.py

Si eso no proporciona suficiente control, en lugar del método waitForCompletion puede agregar una función de callback al objeto monitorManager (que se crea automáticamente cuando importa el módulo abaqus: from abaqus import * ). Esto le permite monitorear y responder a varios mensajes del solucionador, como COMPLETED , ITERATION , etc. La función de callback se define como:

 def onMessage(jobName, messageType, data, userData): if messageType == COMPLETED: # do stuff else: # other stuff 

Que luego se agrega al monitorManager y el trabajo se llama:

 monitorManager.addMessageCallback(jobName=MyJobName, messageType=ANY_MESSAGE_TYPE, callback=onMessage, userData=MyDataObj) j = mdb.Job(name=MyJobName, model=MyModelName) j.submit() 

Uno de los beneficios de este enfoque es que puede pasar un objeto de Python como el argumento userData . Esto podría ser potencialmente su archivo de salida, o algún otro contenedor de datos. Probablemente podría descubrir cómo procesar los datos de salida dentro de la función de callback; por ejemplo, acceda a Odb y obtenga los datos, luego realice las manipulaciones necesarias sin necesidad de un archivo externo.

Estoy de acuerdo con la respuesta, excepto por algunos problemas menores de syntax.

Definir variables de instancia dentro del manejador es un no, no. Sin mencionar que no se están definiendo en ningún tipo de método init (). Subclase TCPServer y defina sus variables de instancia en TCPServer. init (). Todo lo demás funcionará igual.