How to manually create a discipline interfacing an external executable?¶
Presentation of the problem¶
Let’s consider a binary software computing the float output \(c = a^2 + b^2\) from two float inputs : 'a'
and 'b'
.
The inputs are read in the inputs.txt
file which looks like: a=1 b=2
and the output is written to: outputs.txt
which looks like c=5
.
Then, the executable can be run using the shell command ./run.ksh
:
#!/bin/bash
set -i
echo "Parsed inputs.txt file"
source "inputs.txt"
echo "a="$a
echo "b="$b
echo "executing simulation..."
c=$(perl -e "print $a*$a+$b*$b")
echo "Done."
echo "Computed output : c = a**2+b**2 = "$c
echo "c="$c>"outputs.txt"
echo "Wrote output file 'outputs.txt'"
Let’s make a discipline out of this from an initial 'inputs.txt'
.
Implementation of the discipline¶
The construction of an MDODiscipline
consists in three steps:
Instantiate the
MDODiscipline
using the super constructor,Initialize the grammars using the
JSONGrammar.update()
method,Set the default inputs from the initial
inputs.txt
.
The MDODiscipline._run
method consists in three steps:
Get the input data from
MDODiscipline.local_data
and write theinputs.txt
file,Run the executable using the
os.system()
command (`https://docs.python.org/3/library/os.html#os.system`_),Get the output values and store them to
MDODiscipline.local_data
.
Now you can implement the discipline in the following way:
import os
from gemseo.core.discipline import MDODiscipline
class ShellExecutableDiscipline(MDODiscipline):
def __init__(self):
super(ShellExecutableDiscipline, self).__init__("ShellDisc")
# Initialize the grammars
self.input_grammar.update(['a','b'])
self.output_grammar.update(['c'])
# Initialize the default inputs
self.default_inputs=parse_file("inputs.txt")
def _run(self):
# Write inputs.txt file
write_file(self.local_data, 'inputs.txt')
# Run the executable from the inputs
os.system('./run.ksh')
# Parse the outputs.txt file
outputs = parse_file('outputs.txt')
# Store the outputs
self.local_data.update(outputs)
where parse_file()
and write_file()
functions are defined by:
from numpy import array
def parse_file(file_path):
data={}
with open(file_path) as inf:
for line in inf.readlines():
if len(line)==0:
continue
name,value=line.replace("\n","").split("=")
data[name]=array([float(value)])
return data
def write_file(data, file_path):
with open(file_path, "w") as outf:
for name,value in data.iteritems():
outf.write(name+"="+str(value[0])+"\n")
Execution of the discipline¶
Now we can run it with default input values:
shell_disc = ShellExecutableDiscipline()
print(shell_disc.execute())
which results in:
Inputs = {'a': array([ 1.]), 'b': array([ 2.])}
Running executable
Outputs = {'c': array([ 5.])}
{'a': array([ 1.]), 'c': array([ 5.]), 'b': array([ 2.])}
or run it with new input values:
print(shell_disc.execute({'a': array([2.]), 'b': array([3.])}))
which results in:
Inputs = {'a': array([ 2.]), 'b': array([ 3.])}
Running executable
Outputs = {'c': array([ 13.])}
{'a': array([ 2.]), 'c': array([ 13.]), 'b': array([ 3.])}