From 6f5f5f1a9f2a0266430eecd903da3a7581ec5e3f Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Thu, 8 Sep 2022 23:05:59 +0200 Subject: [PATCH 1/8] - Fixes circular reference with OMParser - Fix OMPython usage with overrideparameters - Capture stdout of the model (tested with POSIX only so far) --- .../{OMParser/__init__.py => OMParser.py} | 0 OMPython/__init__.py | 73 ++++++++++++++----- 2 files changed, 55 insertions(+), 18 deletions(-) rename OMPython/{OMParser/__init__.py => OMParser.py} (100%) mode change 100755 => 100644 mode change 100755 => 100644 OMPython/__init__.py diff --git a/OMPython/OMParser/__init__.py b/OMPython/OMParser.py old mode 100755 new mode 100644 similarity index 100% rename from OMPython/OMParser/__init__.py rename to OMPython/OMParser.py diff --git a/OMPython/__init__.py b/OMPython/__init__.py old mode 100755 new mode 100644 index 82c20e45..caf0837e --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -63,7 +63,8 @@ class which means it will use OMCSessionZMQ by default. If you want to use sys.path.append('/opt/openmodelica/lib/python2.7/site-packages/') # TODO: replace this with the new parser -from OMPython import OMTypedParser, OMParser +# from OMPython import OMTypedParser, OMParser +from . import OMTypedParser, OMParser __license__ = """ This file is part of OpenModelica. @@ -206,11 +207,8 @@ def _start_omc_process(self, timeout): my_env["PATH"] = omhome_bin + os.pathsep + my_env["PATH"] self._omc_process = subprocess.Popen(self._omc_command, stdout=self._omc_log_file, stderr=self._omc_log_file, env=my_env) else: - # set the user environment variable so omc running from wsgi has the same user as OMPython - my_env = os.environ.copy() - my_env["USER"] = self._currentUser # Because we spawned a shell, and we need to be able to kill OMC, create a new process group for this - self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file, env=my_env, preexec_fn=os.setsid) + self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file, preexec_fn=os.setsid) if self._docker: for i in range(0,40): try: @@ -788,7 +786,7 @@ def sendExpression(self, command, parsed=True): class ModelicaSystem(object): - def __init__(self, fileName=None, modelName=None, lmodel=[], useCorba=False, commandLineOptions=None, variableFilter=None): # 1 + def __init__(self, fileName=None, modelName=None, lmodel=[], useCorba=False, commandLineOptions=None, variableFilter=None, xmlFileName=None): # 1 """ "constructor" It initializes to load file and build a model, generating object, exe, xml, mat, and json files. etc. It can be called : @@ -826,10 +824,13 @@ def __init__(self, fileName=None, modelName=None, lmodel=[], useCorba=False, com self.linearoutputs = [] # linearization output list self.linearstates = [] # linearization states list - if useCorba: - self.getconn = OMCSession() + if xmlFileName is None: + if useCorba: + self.getconn = OMCSession() + else: + self.getconn = OMCSessionZMQ() else: - self.getconn = OMCSessionZMQ() + self.getconn = None ## set commandLineOptions if provided by users if commandLineOptions is not None: @@ -837,6 +838,7 @@ def __init__(self, fileName=None, modelName=None, lmodel=[], useCorba=False, com self.getconn.sendExpression(exp) self.xmlFile = None + self.xmlFileName = xmlFileName self.lmodel = lmodel # may be needed if model is derived from other model self.modelName = modelName # Model class name self.fileName = fileName # Model file/package name @@ -920,7 +922,11 @@ def __loadingModel(self): print("| info | loadLibrary() failed, Unknown type detected: ", element , " is of type ", type(element), ", The following patterns are supported\n1)[\"Modelica\"]\n2)[(\"Modelica\",\"3.2.3\"), \"PowerSystems\"]\n") if loadmodelError: print(loadmodelError) - self.buildModel() + if self.xmlFileName is None: + self.buildModel() + else: + self.xmlFile = self.xmlFileName + self.xmlparse() def buildModel(self, variableFilter=None): if variableFilter is not None: @@ -929,15 +935,13 @@ def buildModel(self, variableFilter=None): if self.variableFilter is not None: varFilter = "variableFilter=" + "\"" + self.variableFilter + "\"" else: - varFilter = "variableFilter=" + "\".*""\"" - # print(varFilter) + varFilter = "variableFilter=" + "\".*""\"" + # buildModelResult=self.getconn.sendExpression("buildModel("+ mName +")") buildModelResult = self.requestApi("buildModel", self.modelName, properties=varFilter) buildModelError = self.requestApi("getErrorString") - # Issue #145. Always print the getErrorString since it might contains build warnings. - if buildModelError: - print(buildModelError) if ('' in buildModelResult): + print(buildModelError) return self.xmlFile=os.path.join(os.path.dirname(buildModelResult[0]),buildModelResult[1]).replace("\\","/") self.xmlparse() @@ -1212,13 +1216,27 @@ def getOptimizationOptions(self, names=None): # 10 return ([self.optimizeOptions.get(x,"NotExist") for x in names]) # to simulate or re-simulate model - def simulate(self,resultfile=None,simflags=None): # 11 + def simulate(self,resultfile=None,simflags=None,overrideaux=None): # 11 """ This method simulates model according to the simulation options. + + Parameters + ---------- + resultfile : str or None + Output file name + + simflags : str or None + Other simulation options not '-override' parameters + + overrideaux : str or None + Specify 'outputFormat' and 'variableFilter + usage + ----- >>> simulate() >>> simulate(resultfile="a.mat") >>> simulate(simflags="-noEventEmit -noRestart -override=e=0.3,g=10) set runtime simulation flags + >>> simulate(simflags="-noEventEmit -noRestart" ,overrideaux="outputFormat=csv,variableFilter=.*") """ if(resultfile is None): r="" @@ -1240,6 +1258,12 @@ def simulate(self,resultfile=None,simflags=None): # 11 override =" -override=" + values1 else: override ="" + # add override flags not parameters or simulation options + if overrideaux: + if override: + override = override + "," + overrideaux + else: + override = " -override=" + overrideaux if (self.inputFlag): # if model has input quantities for i in self.inputlist: @@ -1256,16 +1280,21 @@ def simulate(self,resultfile=None,simflags=None): # 11 if val[0][0] < float(self.simulateOptions["startTime"]): print('Input time value is less than simulation startTime for inputs', i) return - self.__simInput() # create csv file + # self.__simInput() # create csv file # commented by Joerg csvinput=" -csvInput=" + self.csvFile else: csvinput="" + if self.xmlFileName is not None: + cwd_current = os.getcwd() + os.chdir(os.path.join(os.path.dirname(self.xmlFileName))) + if (platform.system() == "Windows"): getExeFile = os.path.join(os.getcwd(), '{}.{}'.format(self.modelName, "exe")).replace("\\", "/") else: getExeFile = os.path.join(os.getcwd(), self.modelName).replace("\\", "/") + out = None if (os.path.exists(getExeFile)): cmd = getExeFile + override + csvinput + r + simflags #print(cmd) @@ -1278,11 +1307,19 @@ def simulate(self,resultfile=None,simflags=None): # 11 p.wait() p.terminate() else: - os.system(cmd) + # os.system(cmd) # Original code + # p = subprocess.Popen([cmd], stdout=subprocess.PIPE) + print(str(cmd)) + p = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # p = subprocess.run([getExeFile, r], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out = p.stdout # .read() self.simulationFlag = True + if self.xmlFileName is not None: + os.chdir(cwd_current) else: raise Exception("Error: application file not generated yet") + return out # to extract simulation results From ddcf90e3ffa0dd6e5e2a8449cf87ca553c7133b5 Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Sun, 11 Sep 2022 17:09:29 +0200 Subject: [PATCH 2/8] Integrate minor upstream changes after review --- OMPython/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index caf0837e..8b1a3985 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -63,7 +63,6 @@ class which means it will use OMCSessionZMQ by default. If you want to use sys.path.append('/opt/openmodelica/lib/python2.7/site-packages/') # TODO: replace this with the new parser -# from OMPython import OMTypedParser, OMParser from . import OMTypedParser, OMParser __license__ = """ @@ -207,8 +206,11 @@ def _start_omc_process(self, timeout): my_env["PATH"] = omhome_bin + os.pathsep + my_env["PATH"] self._omc_process = subprocess.Popen(self._omc_command, stdout=self._omc_log_file, stderr=self._omc_log_file, env=my_env) else: + # set the user environment variable so omc running from wsgi has the same user as OMPython + my_env = os.environ.copy() + my_env["USER"] = self._currentUser # Because we spawned a shell, and we need to be able to kill OMC, create a new process group for this - self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file, preexec_fn=os.setsid) + self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file, env=my_env, preexec_fn=os.setsid) if self._docker: for i in range(0,40): try: @@ -940,8 +942,10 @@ def buildModel(self, variableFilter=None): # buildModelResult=self.getconn.sendExpression("buildModel("+ mName +")") buildModelResult = self.requestApi("buildModel", self.modelName, properties=varFilter) buildModelError = self.requestApi("getErrorString") - if ('' in buildModelResult): + # Issue #145. Always print the getErrorString since it might contains build warnings. + if buildModelError: print(buildModelError) + if ('' in buildModelResult): return self.xmlFile=os.path.join(os.path.dirname(buildModelResult[0]),buildModelResult[1]).replace("\\","/") self.xmlparse() From 2003c9d8cb82b305ae2017ae796cb42c67a79e20 Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Mon, 12 Sep 2022 22:28:20 +0200 Subject: [PATCH 3/8] documentation on the usage of variableFilter and xmlFileName --- OMPython/__init__.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 8b1a3985..78342921 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -792,9 +792,19 @@ def __init__(self, fileName=None, modelName=None, lmodel=[], useCorba=False, com """ "constructor" It initializes to load file and build a model, generating object, exe, xml, mat, and json files. etc. It can be called : - •without any arguments: In this case it neither loads a file nor build a model. This is useful when a FMU needed to convert to Modelica model - •with two arguments as file name with ".mo" extension and the model name respectively - •with three arguments, the first and second are file name and model name respectively and the third arguments is Modelica standard library to load a model, which is common in such models where the model is based on the standard library. For example, here is a model named "dcmotor.mo" below table 4-2, which is located in the directory of OpenModelica at "C:\\OpenModelica1.9.4-dev.beta2\\share\\doc\\omc\\testmodels". + - without any arguments: In this case it neither loads a file nor build a model. + This is useful when a FMU needed to convert to Modelica model + - with two arguments as file name with ".mo" extension and the model name respectively + - with three arguments, the first and second are file name and model name respectively and the third arguments + is Modelica standard library to load a model, which is common in such models where the model is based on + the standard library. For example, here is a model named "dcmotor.mo" below table 4-2, which is located in the + directory of OpenModelica at "C:\\OpenModelica1.9.4-dev.beta2\\share\\doc\\omc\\testmodels". + - with two or three arguments and varableFilter to pick the list of variables that will be written by default when + calling simulate() as csv-string. + Another option to achieve the same, is using the overrideaux when calling simulate() + - with two or three arguments and xmlFileName to instantiate the model only and skip buildModel. + xmlFileName points to modelname_init.xml of a model that has been already built, e.g. by instantiating ModelicaSystem + without xmlFilename or .mos file. Note: If the model file is not in the current working directory, then the path where file is located must be included together with file name. Besides, if the Modelica model contains several different models within the same package, then in order to build the specific model, in second argument, user must put the package name with dot(.) followed by specific model name. ex: myModel = ModelicaSystem("ModelicaModel.mo", "modelName") """ From 0c4c3ac4842516248f52ce8c1ef01400915dce28 Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Mon, 12 Sep 2022 22:37:05 +0200 Subject: [PATCH 4/8] increment minor version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 197be7dd..5a23e44f 100755 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ def generateIDL(): OMPython_packages.extend(['OMPythonIDL', 'OMPythonIDL._OMCIDL', 'OMPythonIDL._OMCIDL__POA']) setup(name='OMPython', - version='3.4.0', + version='3.5.0', description='OpenModelica-Python API Interface', author='Anand Kalaiarasi Ganeson', author_email='ganan642@student.liu.se', From 0aee81d33fc003f3b064b6f26a5cb9662a5470ff Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Tue, 13 Sep 2022 00:07:03 +0200 Subject: [PATCH 5/8] Fixed obsolete file location --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5a23e44f..47e2657c 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def generateIDL(): except ImportError: hasomniidl = False -OMPython_packages = ['OMPython', 'OMPython.OMParser'] +OMPython_packages = ['OMPython'] if hasomniidl: OMPython_packages.extend(['OMPythonIDL', 'OMPythonIDL._OMCIDL', 'OMPythonIDL._OMCIDL__POA']) From 2282545039fe44c7d9c68fd5708851d7e8ec84d5 Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Tue, 13 Sep 2022 00:15:00 +0200 Subject: [PATCH 6/8] Fixed Python 2 syntax --- OMPython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 78342921..a651e6bd 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -1323,7 +1323,7 @@ def simulate(self,resultfile=None,simflags=None,overrideaux=None): # 11 else: # os.system(cmd) # Original code # p = subprocess.Popen([cmd], stdout=subprocess.PIPE) - print(str(cmd)) + # print(str(cmd)) p = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # p = subprocess.run([getExeFile, r], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = p.stdout # .read() From c663b97212a56da133b1536c0f7c7208ba6e50b2 Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Tue, 13 Sep 2022 00:21:46 +0200 Subject: [PATCH 7/8] Yet another Python 2 compatibility fix --- OMPython/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index a651e6bd..c23d4c4f 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -1324,7 +1324,10 @@ def simulate(self,resultfile=None,simflags=None,overrideaux=None): # 11 # os.system(cmd) # Original code # p = subprocess.Popen([cmd], stdout=subprocess.PIPE) # print(str(cmd)) - p = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + try: # Python 3 + p = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except: # Python 2 + p = subprocess.call(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # p = subprocess.run([getExeFile, r], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = p.stdout # .read() self.simulationFlag = True From d93a9b1ee58afd12573d7dc1cfda0d78a8d510de Mon Sep 17 00:00:00 2001 From: Joerg Wangemann Date: Tue, 13 Sep 2022 00:27:28 +0200 Subject: [PATCH 8/8] Yet one another Python 2 compatibility fix --- OMPython/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index c23d4c4f..d096c6d9 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -1326,10 +1326,11 @@ def simulate(self,resultfile=None,simflags=None,overrideaux=None): # 11 # print(str(cmd)) try: # Python 3 p = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out = p.stdout # .read() except: # Python 2 - p = subprocess.call(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + os.system(cmd) + out = '' # p = subprocess.run([getExeFile, r], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - out = p.stdout # .read() self.simulationFlag = True if self.xmlFileName is not None: os.chdir(cwd_current)