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 Discipline consists in three steps:
Instantiate the
Disciplineusing the super constructor,Initialize the grammars using the
BaseGrammar.update_from_names()method,Set the default inputs from the initial
inputs.txt.
The Discipline._run method consists in three steps:
Write the
inputs.txtfile,Run the executable using the
os.system()command (https://docs.python.org/3/library/os.html#os.system),Return the outputs.
Now you can implement the discipline in the following way:
import os
from gemseo.core.discipline import Discipline
class ShellExecutableDiscipline(Discipline):
def __init__(self):
super().__init__()
# Initialize the grammars
self.input_grammar.update_from_names(['a','b'])
self.output_grammar.update_from_names(['c'])
# Initialize the default inputs
self.default_input_data=parse_file("inputs.txt")
def _run(self, input_data):
# Write inputs.txt file
write_file(input_data, 'inputs.txt')
# Run the executable from the inputs
os.system('./run.ksh')
# Parse and return the outputs.txt file
return parse_file('outputs.txt')
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.])}