Skip to content
Permalink
Browse files

Merge pull request #13299 from aeslaughter/python-mms

Add support for known functions to be included in MMS evaluation
  • Loading branch information...
moosebuild committed Apr 24, 2019
2 parents 86c070a + ab82633 commit 06ec391d97ebfb1c18c2fc0d7361e54d9029f1fb
@@ -88,15 +88,22 @@ exact solution. The complete output for this function is given below.

The `mms.evaluate` function automatically invokes 'x', 'y', 'z', and 't' as available variables; the
first argument is the partial differential equation to solve (the strong form) and the second is the
assumed known solution.
assumed known solution. The variable 'R' is reserved as the coordinate system
(see [sympy.vector.CoordSys3D](https://docs.sympy.org/latest/modules/vector/coordsys.html optional=True) for more information.)
Finally, the variables 'e_i', 'e_j', and 'e_k' are also reserved, each represents the basis vectors
for a 3D coordinate system. These are simply alternate variables for 'R.i', 'R.j', and 'R.k'.

The following table lists the additional arguments that may be passed to the "evaluate" function.

| Keyword | Type | Default | Description |
| :- | :- | :- | :- |
| `variable` | `str` | `u` | The primary variable to be solved for within the PDE |
| `scalars` | `list` | | A list of arbitrary +scalar+ variables included in the solution or PDE |
| `vectors` | `list` | | A list of arbitrary +vector+ variables included in the solution or PDE |
| `scalars` | `list` | | A list of constant +scalar+ variables included in the solution or PDE |
| `vectors` | `list` | | A list of constant +vector+ variables included in the solution or PDE |
| `functions` | `list` | | A list of arbitrary functions of 'x', 'y', 'z', and 't' in the solution or PDE |
| `vectorfunctions` | `list` | | A list of arbitrary vector functions of 'x', 'y', 'z', and 't' in the solution or PDE |
| `negative` | `bool` | `False` | Flag for returning the negative of the evaluated function, by default this is false thus the function returned is correct for placing in the [BodyForce](BodyForce.md) Kernel object. |
| `**kwargs` | `dict` | | All additional key, value pairs supplied are evaluated as additional functions that may include the 'x', 'y', 'z', and 't' or any other variables defined in the previous arguments. If 'e_i', 'e_j', 'e_k' are supplied the supplied function components (e.g., '_x', '_y', and '_z') are automatically defined. |

When arbitrary vectors are supplied, the output will include the components named using the
vector name with "_x", "_y", or "_z". For example, the following example executed in
@@ -233,7 +240,7 @@ as well as performs a "diff" with the data generated from the calls to the `run_
method on the object returned from the run functions. For example, the spatial script contains
the following lines at the end of the script to create the files that is tested.
!listing mms_spatial.py start=TESTING
!listing mms_spatial.py start=TESTING include-start=False
!bibtex bibliography
@@ -68,7 +68,7 @@ def _fit(self, x, y):
Key, value Options:
x[float]: The x-position in data coordinates.
y[float]: The y-poisition in data coordinates.
y[float]: The y-position in data coordinates.
"""

# Perform fit
@@ -3,19 +3,31 @@
from fparser import print_fparser
from moosefunction import print_moose

def evaluate(pde, soln, variable='u', scalars=set(), vectors=set(), functions=set(), vectorfunctions=set()):
def evaluate(pde, soln, variable='u',
scalars=set(),
vectors=set(),
functions=set(),
vectorfunctions=set(),
negative=False,
**kwargs):
"""
Function to evaluate PDEs for the method of manufactured solutions forcing functions.
Inputs:
pde[str]: A string representation of your PDE, eg. 'diff(T,t) + div(grad(T))'
soln[str]: The desired solution, e.g., 'cos(u*t*x)'
variable[str]: The solution variable in the PDE (default: 'u')
scalars[list]: A list of strings of constant extra scalar variables
scalars[list]: A list of strings of constant scalar variables
vectors[list]: A list of strings of extra constant vector variables
functions[list]: A list of strings of scalar variables that are a function of x,y,z, and t.
vector_functions[list]: A list of strings of generic vector variables that are a function of
x,y,z, and t.
functions[list]: A list of strings of arbitrary scalar variables that are a function of
x,y,z, and t.
vectorfunctions[list]: A list of strings of arbitrary vector variables that are a function
of x,y,z, and t.
negative[bool]: If true the negative of the computed function is returned, by default this
is false. Thus, by default the return function may be pasted directly
into the BodyForce Kernel object within MOOSE.
kwargs: Variables with known functions that are a function of x,y,z, and t or any other
symbol, e.g. "u='cos(x*y)'" or "y='2*y*y*R.i + 5*x*R.j'".
Example:
import mms
@@ -30,9 +42,13 @@ def evaluate(pde, soln, variable='u', scalars=set(), vectors=set(), functions=se
y = R.y
z = R.z
t = Symbol('t')
e_i = R.i
e_j = R.j
e_k = R.k

# Define extra vectors, use _v_ in order to not collide with vector=['v']
for _v_ in vectors:
_check_reserved(_v_)
for _c_ in 'xyz':
_s_ = '{}_{}'.format(_v_, _c_)
locals()[_s_] = Symbol(_s_)
@@ -42,24 +58,60 @@ def evaluate(pde, soln, variable='u', scalars=set(), vectors=set(), functions=se

# Define extra scalars
for _s_ in scalars:
_check_reserved(_s_)
locals()[_s_] = Symbol(_s_)

# Define extra functions
for _f_ in functions:
_check_reserved(_f_)
locals()[_f_] = Function(_f_)(x, y, z, t)

# Define extra vector functions
for _vf_ in vectorfunctions:
_check_reserved(_vf_)
for _c_ in 'xyz':
_s_ = '{}_{}'.format(_vf_, _c_)
locals()[_s_] = Function(_s_)(x, y, z, t)
locals()[_vf_] = locals()['{}_x'.format(_vf_)]*R.i + \
locals()['{}_y'.format(_vf_)]*R.j + \
locals()['{}_z'.format(_vf_)]*R.k

# Evaluate the solution
# Define known functions
for _f_, _v_ in kwargs.iteritems():
_check_reserved(_f_)
locals()[_f_] = eval(_v_)
if isinstance(locals()[_f_], Vector):
locals()['{}_x'.format(_f_)] = locals()[_f_].components.get(R.i, 0)
locals()['{}_y'.format(_f_)] = locals()[_f_].components.get(R.j, 0)
locals()['{}_z'.format(_f_)] = locals()[_f_].components.get(R.k, 0)

locals()[variable] = eval(soln)

# Evaluate the PDE
pde = pde.replace('grad', 'gradient')
pde = pde.replace('div', 'divergence')
return eval(pde), locals()[variable]
if negative:
return -eval(pde), locals()[variable]
else:
return eval(pde), locals()[variable]

def _check_reserved(var):
"""Error checking for input variables."""
if var == 'R':
raise SyntaxError("The variable name 'R' is reserved, it represents the coordinate system," \
" see sympy.vector.CoordSys3D.")

elif var in ['x', 'y', 'z']:
msg = "The variable name '{0}' is reserved, it represents the {0} spatial direction " \
"(R.{0}) for the 'R' coordinate system as defined by a sympy.vector.CoordSys3D."
raise SyntaxError(msg.format(var))

elif var == 't':
raise SyntaxError("The variable name 't' is reserved, it represents time.")


elif var in ['e_i', 'e_j', 'e_k']:
basis = dict(e_i='x', e_j='y', e_k='z')
msg = "The variable name '{0}' is reserved, it represents the {1} basis vector " \
"(R.{0}) for the 'R' coordinate system as defined by a sympy.vector.CoordSys3D."
raise SyntaxError(msg.format(var, basis[var]))
@@ -32,6 +32,7 @@ def _runner(input_file, num_refinements, *args, **kwargs):
executable = kwargs.get('executable', None)
csv = kwargs.get('csv', None)
console = kwargs.get('console', True)
mpi = kwargs.get('mpi', None)
rtype = kwargs.get('rtype') # SPATIAL or TEMPORAL
dt = kwargs.pop('dt', 1) # only used with rtype=TEMPORAL

@@ -66,7 +67,7 @@ def _runner(input_file, num_refinements, *args, **kwargs):
dt = dt / 2.

print 'Running:', executable, ' '.join(a)
out = mooseutils.run_executable(executable, a, suppress_output=not console)
out = mooseutils.run_executable(executable, a, mpi=mpi, suppress_output=not console)

# Check that CSV file exists
if not os.path.isfile(csv):
@@ -42,6 +42,34 @@ def testEvaluateWithVectorFunction(self):
'cos(x*t)*Derivative(h_y(R.x, R.y, R.z, t), R.y) + ' \
'cos(x*t)*Derivative(h_z(R.x, R.y, R.z, t), R.z)')

def testEvaluateWithKwargs(self):
f, _ = mms.evaluate('div(h*u)', 'cos(x*t)*e_i', scalars=['k'], h='k*x*x')
s = mms.fparser(f)

self.assertEqual(s, '-x^2*k*t*sin(x*t) + 2*x*k*cos(x*t)')

def testExceptions(self):

try:
mms.evaluate('div(h*u)', 'cos(x*t)*e_i', scalars=['R'], h='k*x*x')
except SyntaxError as e:
self.assertIn("name 'R'", e.message)

try:
mms.evaluate('div(h*u)', 'cos(x*t)*e_i', scalars=['x'], h='k*x*x')
except SyntaxError as e:
self.assertIn("name 'x'", e.message)

try:
mms.evaluate('div(h*u)', 'cos(x*t)*e_i', scalars=['t'], h='k*x*x')
except SyntaxError as e:
self.assertIn("name 't'", e.message)

try:
mms.evaluate('div(h*u)', 'cos(x*t)*e_i', scalars=['e_k'], h='k*x*x')
except SyntaxError as e:
self.assertIn("name 'e_k'", e.message)

def testHit(self):
f,s = mms.evaluate('a*div(k*grad(u))', 'x**3', scalars=['k', 'a'])
n = str(mms.build_hit(f, 'force', a=42))
@@ -146,12 +146,15 @@ def find_moose_executable_recursive(loc, **kwargs):
break
return executable

def run_executable(app_path, args, suppress_output=False):
def run_executable(app_path, args, mpi=None, suppress_output=False):
"""
A function for running an application.
"""
import subprocess
cmd = [app_path]
if mpi and isinstance(mpi, int):
cmd = ['mpiexec', '-n', str(mpi), app_path]
else:
cmd = [app_path]
cmd += args

if suppress_output:

0 comments on commit 06ec391

Please sign in to comment.
You can’t perform that action at this time.