Связь между двумя отдельными движками Python

Постановка проблемы следующая:

Я работаю с Abaqus, программой для анализа механических проблем. По сути, это автономный интерпретатор Python со своими собственными объектами и т. д. В этой программе я запускаю скрипт Python для настройки моего анализа (поэтому этот скрипт можно изменить). Он также содержит метод, который должен выполняться при получении внешнего сигнала. Эти сигналы поступают из основного скрипта, который я запускаю в своем собственном движке Python.

На данный момент у меня есть следующий рабочий процесс: основной сценарий устанавливает логическое значение в True, когда сценарий Abaqus должен выполнить определенную функцию, и собирает это логическое значение в файл. Сценарий Abaqus регулярно проверяет этот файл, чтобы убедиться, что для логического значения установлено значение true. Если это так, он анализирует и собирает выходные данные, чтобы основной сценарий мог прочитать эти выходные данные и действовать в соответствии с ними.

Я ищу более эффективный способ сигнализировать другому процессу о начале анализа, поскольку, как известно, происходит много ненужных проверок. Обмен данными через pickle для меня не проблема, но более эффективное решение, безусловно, приветствуется.

Результаты поиска всегда дают мне решения с подпроцессом или чем-то подобным, то есть для двух процессов, запущенных в одном интерпретаторе. Я также смотрел на ZeroMQ, так как он должен достигать таких целей, но я думаю, что это излишество, и мне нужно решение на python. Оба интерпретатора работают под управлением Python 2.7 (хотя и разные версии).


person mooisjken    schedule 10.04.2017    source источник
comment
Каковы требования к этим двум процессам? Должны ли они быть отдельными процессами? Они должны работать на отдельных машинах? Отдельные идентификаторы пользователей? Почему вы выбрали два процесса для своего подхода и какие существуют ограничения?   -  person aghast    schedule 11.04.2017
comment
разве вы не можете структурировать вещи так, чтобы процесс abaqus был основным, а другой вызывался как подпроцесс?   -  person agentp    schedule 11.04.2017
comment
Возможно, на самом деле лучше, чтобы основной процесс был средой python 3.x, и чтобы он вызывал, например, "abaqus python aq_script.py", который считывает и собирает abaqus данные. А затем еще один сценарий вывода обратно в abaqus вызывается таким же образом после повторного травления. Это медленно, но если вам нужно scipy или tensorflow и т. д., это все равно будет быстрее, чем реализация в древней реализации numpy abaqus.   -  person Daniel F    schedule 11.04.2017
comment
Общение через сокеты не удовлетворит ваши потребности?   -  person Right leg    schedule 12.04.2017
comment
@AustinHastings: оба процесса должны быть отдельными и работать на одном компьютере. Мой «основной скрипт» должен работать на моем основном движке Python, так как ему не нужно запускать abaqus в каждом случае. Таким образом, абакус является лишь частью программы, доступ к которой осуществляется по мере необходимости.   -  person mooisjken    schedule 12.04.2017
comment
@agentp: abaqus вызывается как подпроцесс из моего основного скрипта: subprocess.call('abaqus cae script = test.py',shell=True). Это выводит сообщение о том, что сервер лицензий abaqus запущен, и открывает программу abaqus с заданным скриптом. Мне нужен способ связи с этим скриптом из моего основного скрипта. Структурировать его наоборот нелогично, как указывал мой предыдущий ответ.   -  person mooisjken    schedule 12.04.2017
comment
@DanielForsman: избегание древнего numpy abaqus действительно является одним из преимуществ запуска обеих отдельных программ. Я прав, думая, что ваше решение соответствует моему текущему обходному пути? И как сделать, чтобы Abaqus эффективнее принимал сигнал, вместо того, чтобы без устали проверять изменение в маринованном файле?   -  person mooisjken    schedule 12.04.2017
comment
@Rightleg: во время поиска в Интернете я наткнулся на сокеты, но не очень хорошо понял, как их возможности можно использовать в моей проблеме. Вероятно, это потому, что я не знаком с этим понятием, и его трудно понять. В основном мне нужна какая-то линия связи между обоими процессами. Концептуально говоря, эта линия связи может быть настроена в моем основном сценарии, и ее детали могут быть переданы абакусу при выполнении subprocess.call. Я понятия не имею, как заставить это работать на практике.   -  person mooisjken    schedule 12.04.2017
comment
@mooisjken Могут быть более эффективные способы решения вашей проблемы, чем использование сокетов, но это IMO самое простое решение для реализации. По сути, вы заставляете свои два приложения обмениваться данными через локальный хост. Вам нужно будет разработать протокол в более сложной ситуации, но в Python вы можете легко отправлять (и анализировать при получении) строку, что упрощает понимание. Скажем, вы хотите запустить операцию 5 с параметрами 11 и 32.7: отправьте "operation:5; parameters:(11, 32.7)" из вашего основного скрипта вашему воркеру. В Интернете есть множество примеров кода для программирования сокетов.   -  person Right leg    schedule 12.04.2017


Ответы (3)


Изменить:

Как и @MattP, я добавлю это заявление о моем понимании:

Фон

Я полагаю, что вы используете продукт под названием abaqus. Продукт abaqus включает интерпретатор Python, к которому вы можете получить доступ каким-либо образом (возможно, запустив abaqus python foo.py в командной строке).

У вас также есть отдельная установка Python на той же машине. Вы разрабатываете код, возможно, включая numpy/scipy, для запуска на этой установке python.

Эти две установки разные: у них разные бинарные интерпретаторы, разные библиотеки, разные пути установки и т. д. Но они живут на одном физическом хосте.

Ваша цель состоит в том, чтобы разрешить написанным вами программам на «простом питоне» взаимодействовать с одним или несколькими сценариями, работающими в среде «Abaqus python», чтобы эти сценарии могли выполнять работу внутри системы Abaqus и возвращать результаты.

Решение

Вот решение на основе сокетов. Есть две части, abqlistener.py и abqclient.py. Преимущество этого подхода в том, что он использует четко определенный механизм «ожидания работы». Никакого опроса файлов и т.д. И это "жесткий" API. Вы можете подключиться к процессу-слушателю из процесса на той же машине, где работает та же версия Python, или с другой машины, или из другой версии Python, или из Ruby, или C, или Perl, или даже COBOL. Это позволяет вам создать в вашей системе настоящий «воздушный зазор», чтобы вы могли разрабатывать две части с минимальной связью.

Серверная часть abqlistener. Цель состоит в том, чтобы вы скопировали часть этого кода в свой сценарий Abaqus. Затем процесс abq станет сервером, прослушивающим соединения на определенном номере порта и выполняющим работу в ответ. Отправка ответа или нет. И так далее.

Я не уверен, что вам нужно выполнять настройку для каждого задания. Если да, то это должно быть частью связи. Это просто запускает ABQ, прослушивает порт (навсегда) и обрабатывает запросы. Любая настройка для конкретной работы должна быть частью рабочего процесса. (Возможно, отправьте строку параметров или имя файла конфигурации или что-то еще.)

Клиентская часть abqclient. Это можно переместить в модуль или просто скопировать/вставить в существующий код программы, отличной от ABQ. По сути, вы открываете соединение с правильной комбинацией хост: порт и разговариваете с сервером. Отправить некоторые данные, получить некоторые данные обратно и т. д.

Этот материал в основном взят из примера кода онлайн. Так что это должно выглядеть очень знакомо, если вы начнете во что-то копаться.

Вот 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()

А вот 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()

И вот что они печатают, когда я запускаю их вместе:

$ 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...

Ссылки:

argparse, SocketServer, json, socket — все это "стандартные" библиотеки Python.

person aghast    schedule 12.04.2017
comment
Полностью не проверял, но могу сказать, что по крайней мере операторы import работают в abaqus python, а значит, должно работать. Поскольку я не большой программист, есть ли какие-то подводные камни, которые я должен ожидать при передаче больших данных (1 ГБ+) через сокет? Обычно я хочу передать большой словарь или массив numpy, должно ли это работать или я должен отправить его в виде текстового потока? - person Daniel F; 12.04.2017
comment
Для такого большого количества данных вам придется отправлять его меньшими порциями — скажем, блоками по 4 КБ или 16 КБ или что-то в этом роде. Вам также следует рассмотреть возможность записи файла и передачи имени файла - я не знаю, каким образом вы получите лучшую производительность. - person aghast; 12.04.2017
comment
Так что по-прежнему собираем и распаковываем на диск, но, по крайней мере, основной скрипт и скрипт abaqus могут сообщать друг другу, когда их части закончены. - person Daniel F; 12.04.2017
comment
Убедитесь, что вы открываете файл как двоичный файл, а метод .tofile должен напрямую сбрасывать необработанные данные. Это может ускорить дело. Затем попробуйте использовать .data, чтобы получить доступ к необработанному буферу для прохождения через сокет. Вы ДЕЙСТВИТЕЛЬНО не хотите преобразовывать массив размером 1 ГБ туда и обратно в любой другой формат. - person aghast; 12.04.2017
comment
@AustinHastings: большое спасибо! Это решение было именно тем, что я искал. Хочу отметить небольшую ошибку: клиентская часть должна иметь host='localhost' вместо host=''. И затем обратите внимание: когда вы хотите отправить несколько сообщений с течением времени, вам нужно повторно инициализировать сокет, снова подключиться, затем отправить и получить, а затем снова закрыть. Я не нашел причину, по которой время соединения истекло. Сервер продолжает работать и не требует перезапуска. - person mooisjken; 14.04.2017
comment
Для возможных будущих пользователей Abaqus: это мой текущий рабочий процесс: я запускаю abaqus в своем основном сценарии, используя subprocess.Popen('abaqus cae script=C:\FCP\workspace\TestAbaqus.py', shell=True). Поскольку запуск abaqus занимает некоторое время (около 10 секунд), мы не можем сразу установить соединение в нашем основном скрипте. Поэтому я продолжаю пытаться сделать связь в конструкции while-try-except, пока не получится. После этого остальная часть основного скрипта может работать, так как я знаю, что в этот момент среда abaqus полностью запущена и работает. - person mooisjken; 14.04.2017
comment
Вы closeустанавливаете соединение каждый раз на стороне клиента? Может быть, поэтому вы не можете просто общаться... - person aghast; 14.04.2017

Чтобы было ясно, насколько я понимаю, вы запускаете Abaqus/CAE через сценарий Python как независимый процесс (назовем его abq.py), который проверяет, открывает и считывает файл триггера, чтобы определить, должен ли он запускать анализ. Файл триггера создается вторым процессом Python (назовем его main.py). Наконец, main.py ожидает чтения выходного файла, созданного abq.py. Вам нужен более эффективный способ сигнализировать abq.py о проведении анализа, и вы открыты для различных методов обмена данными.

Как вы упомянули, подпроцесс или многопроцессорность могут быть вариантом. Однако я думаю, что более простым решением является объединение двух ваших сценариев и, при необходимости, использование функции обратного вызова для мониторинга решения и обработки вашего вывода. Я предполагаю, что нет необходимости постоянно запускать abq.py как отдельный процесс, и что все анализы можно запускать из main.py, когда это уместно.

Пусть main.py получит доступ к Abaqus Mdb. Если он уже построен, вы открываете его с помощью:

mdb = openMdb(FileName)

Файл триггера не нужен, если main.py запускает все анализы. Например:

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

После завершения main.py может прочитать выходной файл и продолжить. Это просто, если файл данных был сгенерирован самим анализом (например, файлы .dat или .odb). OTH, если выходной файл создается каким-то кодом в вашем текущем abq.py, то вы, вероятно, можете просто включить его вместо этого в main.py.

Если это не обеспечивает достаточного контроля, вместо метода waitForCompletion вы можете добавить функцию обратного вызова в объект monitorManager (который автоматически создается при импорте модуля abaqus: from abaqus import *). Это позволяет отслеживать и реагировать на различные сообщения от решателя, такие как COMPLETED, ITERATION и т. д. Функция обратного вызова определяется следующим образом:

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

Который затем добавляется в monitorManager и вызывается задание:

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

Одним из преимуществ этого подхода является то, что вы можете передать объект Python в качестве аргумента userData. Потенциально это может быть ваш выходной файл или какой-либо другой контейнер данных. Вероятно, вы могли бы выяснить, как обрабатывать выходные данные в функции обратного вызова - например, получить доступ к Odb и получить данные, а затем выполнить любые манипуляции по мере необходимости, вообще не нуждаясь во внешнем файле.

person Matt P    schedule 11.04.2017

Я согласен с ответом, за исключением некоторых незначительных проблем с синтаксисом.

определение переменных экземпляра внутри обработчика — нет, нет. не говоря уже о том, что они не определяются ни в каком методе init(). Подкласс TCPServer и определите переменные вашего экземпляра в TCPServer.init(). Все остальное будет работать так же.

person commonHuman    schedule 02.05.2018