|
""" |
|
module for generating C, C++, Fortran77, Fortran90, Julia, Rust |
|
and Octave/Matlab routines that evaluate SymPy expressions. |
|
This module is work in progress. |
|
Only the milestones with a '+' character in the list below have been completed. |
|
|
|
--- How is sympy.utilities.codegen different from sympy.printing.ccode? --- |
|
|
|
We considered the idea to extend the printing routines for SymPy functions in |
|
such a way that it prints complete compilable code, but this leads to a few |
|
unsurmountable issues that can only be tackled with dedicated code generator: |
|
|
|
- For C, one needs both a code and a header file, while the printing routines |
|
generate just one string. This code generator can be extended to support |
|
.pyf files for f2py. |
|
|
|
- SymPy functions are not concerned with programming-technical issues, such |
|
as input, output and input-output arguments. Other examples are contiguous |
|
or non-contiguous arrays, including headers of other libraries such as gsl |
|
or others. |
|
|
|
- It is highly interesting to evaluate several SymPy functions in one C |
|
routine, eventually sharing common intermediate results with the help |
|
of the cse routine. This is more than just printing. |
|
|
|
- From the programming perspective, expressions with constants should be |
|
evaluated in the code generator as much as possible. This is different |
|
for printing. |
|
|
|
--- Basic assumptions --- |
|
|
|
* A generic Routine data structure describes the routine that must be |
|
translated into C/Fortran/... code. This data structure covers all |
|
features present in one or more of the supported languages. |
|
|
|
* Descendants from the CodeGen class transform multiple Routine instances |
|
into compilable code. Each derived class translates into a specific |
|
language. |
|
|
|
* In many cases, one wants a simple workflow. The friendly functions in the |
|
last part are a simple api on top of the Routine/CodeGen stuff. They are |
|
easier to use, but are less powerful. |
|
|
|
--- Milestones --- |
|
|
|
+ First working version with scalar input arguments, generating C code, |
|
tests |
|
+ Friendly functions that are easier to use than the rigorous |
|
Routine/CodeGen workflow. |
|
+ Integer and Real numbers as input and output |
|
+ Output arguments |
|
+ InputOutput arguments |
|
+ Sort input/output arguments properly |
|
+ Contiguous array arguments (numpy matrices) |
|
+ Also generate .pyf code for f2py (in autowrap module) |
|
+ Isolate constants and evaluate them beforehand in double precision |
|
+ Fortran 90 |
|
+ Octave/Matlab |
|
|
|
- Common Subexpression Elimination |
|
- User defined comments in the generated code |
|
- Optional extra include lines for libraries/objects that can eval special |
|
functions |
|
- Test other C compilers and libraries: gcc, tcc, libtcc, gcc+gsl, ... |
|
- Contiguous array arguments (SymPy matrices) |
|
- Non-contiguous array arguments (SymPy matrices) |
|
- ccode must raise an error when it encounters something that cannot be |
|
translated into c. ccode(integrate(sin(x)/x, x)) does not make sense. |
|
- Complex numbers as input and output |
|
- A default complex datatype |
|
- Include extra information in the header: date, user, hostname, sha1 |
|
hash, ... |
|
- Fortran 77 |
|
- C++ |
|
- Python |
|
- Julia |
|
- Rust |
|
- ... |
|
|
|
""" |
|
|
|
import os |
|
import textwrap |
|
from io import StringIO |
|
|
|
from sympy import __version__ as sympy_version |
|
from sympy.core import Symbol, S, Tuple, Equality, Function, Basic |
|
from sympy.printing.c import c_code_printers |
|
from sympy.printing.codeprinter import AssignmentError |
|
from sympy.printing.fortran import FCodePrinter |
|
from sympy.printing.julia import JuliaCodePrinter |
|
from sympy.printing.octave import OctaveCodePrinter |
|
from sympy.printing.rust import RustCodePrinter |
|
from sympy.tensor import Idx, Indexed, IndexedBase |
|
from sympy.matrices import (MatrixSymbol, ImmutableMatrix, MatrixBase, |
|
MatrixExpr, MatrixSlice) |
|
from sympy.utilities.iterables import is_sequence |
|
|
|
|
|
__all__ = [ |
|
|
|
"Routine", "DataType", "default_datatypes", "get_default_datatype", |
|
"Argument", "InputArgument", "OutputArgument", "Result", |
|
|
|
"CodeGen", "CCodeGen", "FCodeGen", "JuliaCodeGen", "OctaveCodeGen", |
|
"RustCodeGen", |
|
|
|
"codegen", "make_routine", |
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Routine: |
|
"""Generic description of evaluation routine for set of expressions. |
|
|
|
A CodeGen class can translate instances of this class into code in a |
|
particular language. The routine specification covers all the features |
|
present in these languages. The CodeGen part must raise an exception |
|
when certain features are not present in the target language. For |
|
example, multiple return values are possible in Python, but not in C or |
|
Fortran. Another example: Fortran and Python support complex numbers, |
|
while C does not. |
|
|
|
""" |
|
|
|
def __init__(self, name, arguments, results, local_vars, global_vars): |
|
"""Initialize a Routine instance. |
|
|
|
Parameters |
|
========== |
|
|
|
name : string |
|
Name of the routine. |
|
|
|
arguments : list of Arguments |
|
These are things that appear in arguments of a routine, often |
|
appearing on the right-hand side of a function call. These are |
|
commonly InputArguments but in some languages, they can also be |
|
OutputArguments or InOutArguments (e.g., pass-by-reference in C |
|
code). |
|
|
|
results : list of Results |
|
These are the return values of the routine, often appearing on |
|
the left-hand side of a function call. The difference between |
|
Results and OutputArguments and when you should use each is |
|
language-specific. |
|
|
|
local_vars : list of Results |
|
These are variables that will be defined at the beginning of the |
|
function. |
|
|
|
global_vars : list of Symbols |
|
Variables which will not be passed into the function. |
|
|
|
""" |
|
|
|
|
|
input_symbols = set() |
|
symbols = set() |
|
for arg in arguments: |
|
if isinstance(arg, OutputArgument): |
|
symbols.update(arg.expr.free_symbols - arg.expr.atoms(Indexed)) |
|
elif isinstance(arg, InputArgument): |
|
input_symbols.add(arg.name) |
|
elif isinstance(arg, InOutArgument): |
|
input_symbols.add(arg.name) |
|
symbols.update(arg.expr.free_symbols - arg.expr.atoms(Indexed)) |
|
else: |
|
raise ValueError("Unknown Routine argument: %s" % arg) |
|
|
|
for r in results: |
|
if not isinstance(r, Result): |
|
raise ValueError("Unknown Routine result: %s" % r) |
|
symbols.update(r.expr.free_symbols - r.expr.atoms(Indexed)) |
|
|
|
local_symbols = set() |
|
for r in local_vars: |
|
if isinstance(r, Result): |
|
symbols.update(r.expr.free_symbols - r.expr.atoms(Indexed)) |
|
local_symbols.add(r.name) |
|
else: |
|
local_symbols.add(r) |
|
|
|
symbols = {s.label if isinstance(s, Idx) else s for s in symbols} |
|
|
|
|
|
|
|
|
|
notcovered = symbols.difference( |
|
input_symbols.union(local_symbols).union(global_vars)) |
|
if notcovered != set(): |
|
raise ValueError("Symbols needed for output are not in input " + |
|
", ".join([str(x) for x in notcovered])) |
|
|
|
self.name = name |
|
self.arguments = arguments |
|
self.results = results |
|
self.local_vars = local_vars |
|
self.global_vars = global_vars |
|
|
|
def __str__(self): |
|
return self.__class__.__name__ + "({name!r}, {arguments}, {results}, {local_vars}, {global_vars})".format(**self.__dict__) |
|
|
|
__repr__ = __str__ |
|
|
|
@property |
|
def variables(self): |
|
"""Returns a set of all variables possibly used in the routine. |
|
|
|
For routines with unnamed return values, the dummies that may or |
|
may not be used will be included in the set. |
|
|
|
""" |
|
v = set(self.local_vars) |
|
v.update(arg.name for arg in self.arguments) |
|
v.update(res.result_var for res in self.results) |
|
return v |
|
|
|
@property |
|
def result_variables(self): |
|
"""Returns a list of OutputArgument, InOutArgument and Result. |
|
|
|
If return values are present, they are at the end of the list. |
|
""" |
|
args = [arg for arg in self.arguments if isinstance( |
|
arg, (OutputArgument, InOutArgument))] |
|
args.extend(self.results) |
|
return args |
|
|
|
|
|
class DataType: |
|
"""Holds strings for a certain datatype in different languages.""" |
|
def __init__(self, cname, fname, pyname, jlname, octname, rsname): |
|
self.cname = cname |
|
self.fname = fname |
|
self.pyname = pyname |
|
self.jlname = jlname |
|
self.octname = octname |
|
self.rsname = rsname |
|
|
|
|
|
default_datatypes = { |
|
"int": DataType("int", "INTEGER*4", "int", "", "", "i32"), |
|
"float": DataType("double", "REAL*8", "float", "", "", "f64"), |
|
"complex": DataType("double", "COMPLEX*16", "complex", "", "", "float") |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
COMPLEX_ALLOWED = False |
|
def get_default_datatype(expr, complex_allowed=None): |
|
"""Derives an appropriate datatype based on the expression.""" |
|
if complex_allowed is None: |
|
complex_allowed = COMPLEX_ALLOWED |
|
if complex_allowed: |
|
final_dtype = "complex" |
|
else: |
|
final_dtype = "float" |
|
if expr.is_integer: |
|
return default_datatypes["int"] |
|
elif expr.is_real: |
|
return default_datatypes["float"] |
|
elif isinstance(expr, MatrixBase): |
|
|
|
dt = "int" |
|
for element in expr: |
|
if dt == "int" and not element.is_integer: |
|
dt = "float" |
|
if dt == "float" and not element.is_real: |
|
return default_datatypes[final_dtype] |
|
return default_datatypes[dt] |
|
else: |
|
return default_datatypes[final_dtype] |
|
|
|
|
|
class Variable: |
|
"""Represents a typed variable.""" |
|
|
|
def __init__(self, name, datatype=None, dimensions=None, precision=None): |
|
"""Return a new variable. |
|
|
|
Parameters |
|
========== |
|
|
|
name : Symbol or MatrixSymbol |
|
|
|
datatype : optional |
|
When not given, the data type will be guessed based on the |
|
assumptions on the symbol argument. |
|
|
|
dimensions : sequence containing tuples, optional |
|
If present, the argument is interpreted as an array, where this |
|
sequence of tuples specifies (lower, upper) bounds for each |
|
index of the array. |
|
|
|
precision : int, optional |
|
Controls the precision of floating point constants. |
|
|
|
""" |
|
if not isinstance(name, (Symbol, MatrixSymbol)): |
|
raise TypeError("The first argument must be a SymPy symbol.") |
|
if datatype is None: |
|
datatype = get_default_datatype(name) |
|
elif not isinstance(datatype, DataType): |
|
raise TypeError("The (optional) `datatype' argument must be an " |
|
"instance of the DataType class.") |
|
if dimensions and not isinstance(dimensions, (tuple, list)): |
|
raise TypeError( |
|
"The dimensions argument must be a sequence of tuples") |
|
|
|
self._name = name |
|
self._datatype = { |
|
'C': datatype.cname, |
|
'FORTRAN': datatype.fname, |
|
'JULIA': datatype.jlname, |
|
'OCTAVE': datatype.octname, |
|
'PYTHON': datatype.pyname, |
|
'RUST': datatype.rsname, |
|
} |
|
self.dimensions = dimensions |
|
self.precision = precision |
|
|
|
def __str__(self): |
|
return "%s(%r)" % (self.__class__.__name__, self.name) |
|
|
|
__repr__ = __str__ |
|
|
|
@property |
|
def name(self): |
|
return self._name |
|
|
|
def get_datatype(self, language): |
|
"""Returns the datatype string for the requested language. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Symbol |
|
>>> from sympy.utilities.codegen import Variable |
|
>>> x = Variable(Symbol('x')) |
|
>>> x.get_datatype('c') |
|
'double' |
|
>>> x.get_datatype('fortran') |
|
'REAL*8' |
|
|
|
""" |
|
try: |
|
return self._datatype[language.upper()] |
|
except KeyError: |
|
raise CodeGenError("Has datatypes for languages: %s" % |
|
", ".join(self._datatype)) |
|
|
|
|
|
class Argument(Variable): |
|
"""An abstract Argument data structure: a name and a data type. |
|
|
|
This structure is refined in the descendants below. |
|
|
|
""" |
|
pass |
|
|
|
|
|
class InputArgument(Argument): |
|
pass |
|
|
|
|
|
class ResultBase: |
|
"""Base class for all "outgoing" information from a routine. |
|
|
|
Objects of this class stores a SymPy expression, and a SymPy object |
|
representing a result variable that will be used in the generated code |
|
only if necessary. |
|
|
|
""" |
|
def __init__(self, expr, result_var): |
|
self.expr = expr |
|
self.result_var = result_var |
|
|
|
def __str__(self): |
|
return "%s(%r, %r)" % (self.__class__.__name__, self.expr, |
|
self.result_var) |
|
|
|
__repr__ = __str__ |
|
|
|
|
|
class OutputArgument(Argument, ResultBase): |
|
"""OutputArgument are always initialized in the routine.""" |
|
|
|
def __init__(self, name, result_var, expr, datatype=None, dimensions=None, precision=None): |
|
"""Return a new variable. |
|
|
|
Parameters |
|
========== |
|
|
|
name : Symbol, MatrixSymbol |
|
The name of this variable. When used for code generation, this |
|
might appear, for example, in the prototype of function in the |
|
argument list. |
|
|
|
result_var : Symbol, Indexed |
|
Something that can be used to assign a value to this variable. |
|
Typically the same as `name` but for Indexed this should be e.g., |
|
"y[i]" whereas `name` should be the Symbol "y". |
|
|
|
expr : object |
|
The expression that should be output, typically a SymPy |
|
expression. |
|
|
|
datatype : optional |
|
When not given, the data type will be guessed based on the |
|
assumptions on the symbol argument. |
|
|
|
dimensions : sequence containing tuples, optional |
|
If present, the argument is interpreted as an array, where this |
|
sequence of tuples specifies (lower, upper) bounds for each |
|
index of the array. |
|
|
|
precision : int, optional |
|
Controls the precision of floating point constants. |
|
|
|
""" |
|
|
|
Argument.__init__(self, name, datatype, dimensions, precision) |
|
ResultBase.__init__(self, expr, result_var) |
|
|
|
def __str__(self): |
|
return "%s(%r, %r, %r)" % (self.__class__.__name__, self.name, self.result_var, self.expr) |
|
|
|
__repr__ = __str__ |
|
|
|
|
|
class InOutArgument(Argument, ResultBase): |
|
"""InOutArgument are never initialized in the routine.""" |
|
|
|
def __init__(self, name, result_var, expr, datatype=None, dimensions=None, precision=None): |
|
if not datatype: |
|
datatype = get_default_datatype(expr) |
|
Argument.__init__(self, name, datatype, dimensions, precision) |
|
ResultBase.__init__(self, expr, result_var) |
|
__init__.__doc__ = OutputArgument.__init__.__doc__ |
|
|
|
|
|
def __str__(self): |
|
return "%s(%r, %r, %r)" % (self.__class__.__name__, self.name, self.expr, |
|
self.result_var) |
|
|
|
__repr__ = __str__ |
|
|
|
|
|
class Result(Variable, ResultBase): |
|
"""An expression for a return value. |
|
|
|
The name result is used to avoid conflicts with the reserved word |
|
"return" in the Python language. It is also shorter than ReturnValue. |
|
|
|
These may or may not need a name in the destination (e.g., "return(x*y)" |
|
might return a value without ever naming it). |
|
|
|
""" |
|
|
|
def __init__(self, expr, name=None, result_var=None, datatype=None, |
|
dimensions=None, precision=None): |
|
"""Initialize a return value. |
|
|
|
Parameters |
|
========== |
|
|
|
expr : SymPy expression |
|
|
|
name : Symbol, MatrixSymbol, optional |
|
The name of this return variable. When used for code generation, |
|
this might appear, for example, in the prototype of function in a |
|
list of return values. A dummy name is generated if omitted. |
|
|
|
result_var : Symbol, Indexed, optional |
|
Something that can be used to assign a value to this variable. |
|
Typically the same as `name` but for Indexed this should be e.g., |
|
"y[i]" whereas `name` should be the Symbol "y". Defaults to |
|
`name` if omitted. |
|
|
|
datatype : optional |
|
When not given, the data type will be guessed based on the |
|
assumptions on the expr argument. |
|
|
|
dimensions : sequence containing tuples, optional |
|
If present, this variable is interpreted as an array, |
|
where this sequence of tuples specifies (lower, upper) |
|
bounds for each index of the array. |
|
|
|
precision : int, optional |
|
Controls the precision of floating point constants. |
|
|
|
""" |
|
|
|
if not isinstance(expr, (Basic, MatrixBase)): |
|
raise TypeError("The first argument must be a SymPy expression.") |
|
|
|
if name is None: |
|
name = 'result_%d' % abs(hash(expr)) |
|
|
|
if datatype is None: |
|
|
|
datatype = get_default_datatype(expr) |
|
|
|
if isinstance(name, str): |
|
if isinstance(expr, (MatrixBase, MatrixExpr)): |
|
name = MatrixSymbol(name, *expr.shape) |
|
else: |
|
name = Symbol(name) |
|
|
|
if result_var is None: |
|
result_var = name |
|
|
|
Variable.__init__(self, name, datatype=datatype, |
|
dimensions=dimensions, precision=precision) |
|
ResultBase.__init__(self, expr, result_var) |
|
|
|
def __str__(self): |
|
return "%s(%r, %r, %r)" % (self.__class__.__name__, self.expr, self.name, |
|
self.result_var) |
|
|
|
__repr__ = __str__ |
|
|
|
|
|
|
|
|
|
|
|
|
|
class CodeGen: |
|
"""Abstract class for the code generators.""" |
|
|
|
printer = None |
|
|
|
def _indent_code(self, codelines): |
|
return self.printer.indent_code(codelines) |
|
|
|
def _printer_method_with_settings(self, method, settings=None, *args, **kwargs): |
|
settings = settings or {} |
|
ori = {k: self.printer._settings[k] for k in settings} |
|
for k, v in settings.items(): |
|
self.printer._settings[k] = v |
|
result = getattr(self.printer, method)(*args, **kwargs) |
|
for k, v in ori.items(): |
|
self.printer._settings[k] = v |
|
return result |
|
|
|
def _get_symbol(self, s): |
|
"""Returns the symbol as fcode prints it.""" |
|
if self.printer._settings['human']: |
|
expr_str = self.printer.doprint(s) |
|
else: |
|
constants, not_supported, expr_str = self.printer.doprint(s) |
|
if constants or not_supported: |
|
raise ValueError("Failed to print %s" % str(s)) |
|
return expr_str.strip() |
|
|
|
def __init__(self, project="project", cse=False): |
|
"""Initialize a code generator. |
|
|
|
Derived classes will offer more options that affect the generated |
|
code. |
|
|
|
""" |
|
self.project = project |
|
self.cse = cse |
|
|
|
def routine(self, name, expr, argument_sequence=None, global_vars=None): |
|
"""Creates an Routine object that is appropriate for this language. |
|
|
|
This implementation is appropriate for at least C/Fortran. Subclasses |
|
can override this if necessary. |
|
|
|
Here, we assume at most one return value (the l-value) which must be |
|
scalar. Additional outputs are OutputArguments (e.g., pointers on |
|
right-hand-side or pass-by-reference). Matrices are always returned |
|
via OutputArguments. If ``argument_sequence`` is None, arguments will |
|
be ordered alphabetically, but with all InputArguments first, and then |
|
OutputArgument and InOutArguments. |
|
|
|
""" |
|
|
|
if self.cse: |
|
from sympy.simplify.cse_main import cse |
|
|
|
if is_sequence(expr) and not isinstance(expr, (MatrixBase, MatrixExpr)): |
|
if not expr: |
|
raise ValueError("No expression given") |
|
for e in expr: |
|
if not e.is_Equality: |
|
raise CodeGenError("Lists of expressions must all be Equalities. {} is not.".format(e)) |
|
|
|
|
|
rhs = [e.rhs for e in expr] |
|
common, simplified = cse(rhs) |
|
|
|
|
|
expr = [Equality(e.lhs, rhs) for e, rhs in zip(expr, simplified)] |
|
else: |
|
if isinstance(expr, Equality): |
|
common, simplified = cse(expr.rhs) |
|
expr = Equality(expr.lhs, simplified[0]) |
|
else: |
|
common, simplified = cse(expr) |
|
expr = simplified |
|
|
|
local_vars = [Result(b,a) for a,b in common] |
|
local_symbols = {a for a,_ in common} |
|
local_expressions = Tuple(*[b for _,b in common]) |
|
else: |
|
local_expressions = Tuple() |
|
|
|
if is_sequence(expr) and not isinstance(expr, (MatrixBase, MatrixExpr)): |
|
if not expr: |
|
raise ValueError("No expression given") |
|
expressions = Tuple(*expr) |
|
else: |
|
expressions = Tuple(expr) |
|
|
|
if self.cse: |
|
if {i.label for i in expressions.atoms(Idx)} != set(): |
|
raise CodeGenError("CSE and Indexed expressions do not play well together yet") |
|
else: |
|
|
|
local_vars = {i.label for i in expressions.atoms(Idx)} |
|
local_symbols = local_vars |
|
|
|
|
|
global_vars = set() if global_vars is None else set(global_vars) |
|
|
|
|
|
symbols = (expressions.free_symbols | local_expressions.free_symbols) - local_symbols - global_vars |
|
new_symbols = set() |
|
new_symbols.update(symbols) |
|
|
|
for symbol in symbols: |
|
if isinstance(symbol, Idx): |
|
new_symbols.remove(symbol) |
|
new_symbols.update(symbol.args[1].free_symbols) |
|
if isinstance(symbol, Indexed): |
|
new_symbols.remove(symbol) |
|
symbols = new_symbols |
|
|
|
|
|
return_val = [] |
|
output_args = [] |
|
for expr in expressions: |
|
if isinstance(expr, Equality): |
|
out_arg = expr.lhs |
|
expr = expr.rhs |
|
if isinstance(out_arg, Indexed): |
|
dims = tuple([ (S.Zero, dim - 1) for dim in out_arg.shape]) |
|
symbol = out_arg.base.label |
|
elif isinstance(out_arg, Symbol): |
|
dims = [] |
|
symbol = out_arg |
|
elif isinstance(out_arg, MatrixSymbol): |
|
dims = tuple([ (S.Zero, dim - 1) for dim in out_arg.shape]) |
|
symbol = out_arg |
|
else: |
|
raise CodeGenError("Only Indexed, Symbol, or MatrixSymbol " |
|
"can define output arguments.") |
|
|
|
if expr.has(symbol): |
|
output_args.append( |
|
InOutArgument(symbol, out_arg, expr, dimensions=dims)) |
|
else: |
|
output_args.append( |
|
OutputArgument(symbol, out_arg, expr, dimensions=dims)) |
|
|
|
|
|
if symbol not in local_vars: |
|
|
|
symbols.remove(symbol) |
|
elif isinstance(expr, (ImmutableMatrix, MatrixSlice)): |
|
|
|
out_arg = MatrixSymbol('out_%s' % abs(hash(expr)), *expr.shape) |
|
dims = tuple([(S.Zero, dim - 1) for dim in out_arg.shape]) |
|
output_args.append( |
|
OutputArgument(out_arg, out_arg, expr, dimensions=dims)) |
|
else: |
|
return_val.append(Result(expr)) |
|
|
|
arg_list = [] |
|
|
|
|
|
|
|
|
|
def dimensions(s): |
|
return [(S.Zero, dim - 1) for dim in s.shape] |
|
|
|
array_symbols = {} |
|
for array in expressions.atoms(Indexed) | local_expressions.atoms(Indexed): |
|
array_symbols[array.base.label] = array |
|
for array in expressions.atoms(MatrixSymbol) | local_expressions.atoms(MatrixSymbol): |
|
array_symbols[array] = array |
|
|
|
for symbol in sorted(symbols, key=str): |
|
if symbol in array_symbols: |
|
array = array_symbols[symbol] |
|
metadata = {'dimensions': dimensions(array)} |
|
else: |
|
metadata = {} |
|
|
|
arg_list.append(InputArgument(symbol, **metadata)) |
|
|
|
output_args.sort(key=lambda x: str(x.name)) |
|
arg_list.extend(output_args) |
|
|
|
if argument_sequence is not None: |
|
|
|
new_sequence = [] |
|
for arg in argument_sequence: |
|
if isinstance(arg, IndexedBase): |
|
new_sequence.append(arg.label) |
|
else: |
|
new_sequence.append(arg) |
|
argument_sequence = new_sequence |
|
|
|
missing = [x for x in arg_list if x.name not in argument_sequence] |
|
if missing: |
|
msg = "Argument list didn't specify: {0} " |
|
msg = msg.format(", ".join([str(m.name) for m in missing])) |
|
raise CodeGenArgumentListError(msg, missing) |
|
|
|
|
|
name_arg_dict = {x.name: x for x in arg_list} |
|
new_args = [] |
|
for symbol in argument_sequence: |
|
try: |
|
new_args.append(name_arg_dict[symbol]) |
|
except KeyError: |
|
if isinstance(symbol, (IndexedBase, MatrixSymbol)): |
|
metadata = {'dimensions': dimensions(symbol)} |
|
else: |
|
metadata = {} |
|
new_args.append(InputArgument(symbol, **metadata)) |
|
arg_list = new_args |
|
|
|
return Routine(name, arg_list, return_val, local_vars, global_vars) |
|
|
|
def write(self, routines, prefix, to_files=False, header=True, empty=True): |
|
"""Writes all the source code files for the given routines. |
|
|
|
The generated source is returned as a list of (filename, contents) |
|
tuples, or is written to files (see below). Each filename consists |
|
of the given prefix, appended with an appropriate extension. |
|
|
|
Parameters |
|
========== |
|
|
|
routines : list |
|
A list of Routine instances to be written |
|
|
|
prefix : string |
|
The prefix for the output files |
|
|
|
to_files : bool, optional |
|
When True, the output is written to files. Otherwise, a list |
|
of (filename, contents) tuples is returned. [default: False] |
|
|
|
header : bool, optional |
|
When True, a header comment is included on top of each source |
|
file. [default: True] |
|
|
|
empty : bool, optional |
|
When True, empty lines are included to structure the source |
|
files. [default: True] |
|
|
|
""" |
|
if to_files: |
|
for dump_fn in self.dump_fns: |
|
filename = "%s.%s" % (prefix, dump_fn.extension) |
|
with open(filename, "w") as f: |
|
dump_fn(self, routines, f, prefix, header, empty) |
|
else: |
|
result = [] |
|
for dump_fn in self.dump_fns: |
|
filename = "%s.%s" % (prefix, dump_fn.extension) |
|
contents = StringIO() |
|
dump_fn(self, routines, contents, prefix, header, empty) |
|
result.append((filename, contents.getvalue())) |
|
return result |
|
|
|
def dump_code(self, routines, f, prefix, header=True, empty=True): |
|
"""Write the code by calling language specific methods. |
|
|
|
The generated file contains all the definitions of the routines in |
|
low-level code and refers to the header file if appropriate. |
|
|
|
Parameters |
|
========== |
|
|
|
routines : list |
|
A list of Routine instances. |
|
|
|
f : file-like |
|
Where to write the file. |
|
|
|
prefix : string |
|
The filename prefix, used to refer to the proper header file. |
|
Only the basename of the prefix is used. |
|
|
|
header : bool, optional |
|
When True, a header comment is included on top of each source |
|
file. [default : True] |
|
|
|
empty : bool, optional |
|
When True, empty lines are included to structure the source |
|
files. [default : True] |
|
|
|
""" |
|
|
|
code_lines = self._preprocessor_statements(prefix) |
|
|
|
for routine in routines: |
|
if empty: |
|
code_lines.append("\n") |
|
code_lines.extend(self._get_routine_opening(routine)) |
|
code_lines.extend(self._declare_arguments(routine)) |
|
code_lines.extend(self._declare_globals(routine)) |
|
code_lines.extend(self._declare_locals(routine)) |
|
if empty: |
|
code_lines.append("\n") |
|
code_lines.extend(self._call_printer(routine)) |
|
if empty: |
|
code_lines.append("\n") |
|
code_lines.extend(self._get_routine_ending(routine)) |
|
|
|
code_lines = self._indent_code(''.join(code_lines)) |
|
|
|
if header: |
|
code_lines = ''.join(self._get_header() + [code_lines]) |
|
|
|
if code_lines: |
|
f.write(code_lines) |
|
|
|
|
|
class CodeGenError(Exception): |
|
pass |
|
|
|
|
|
class CodeGenArgumentListError(Exception): |
|
@property |
|
def missing_args(self): |
|
return self.args[1] |
|
|
|
|
|
header_comment = """Code generated with SymPy %(version)s |
|
|
|
See http://www.sympy.org/ for more information. |
|
|
|
This file is part of '%(project)s' |
|
""" |
|
|
|
|
|
class CCodeGen(CodeGen): |
|
"""Generator for C code. |
|
|
|
The .write() method inherited from CodeGen will output a code file and |
|
an interface file, <prefix>.c and <prefix>.h respectively. |
|
|
|
""" |
|
|
|
code_extension = "c" |
|
interface_extension = "h" |
|
standard = 'c99' |
|
|
|
def __init__(self, project="project", printer=None, |
|
preprocessor_statements=None, cse=False): |
|
super().__init__(project=project, cse=cse) |
|
self.printer = printer or c_code_printers[self.standard.lower()]() |
|
|
|
self.preprocessor_statements = preprocessor_statements |
|
if preprocessor_statements is None: |
|
self.preprocessor_statements = ['#include <math.h>'] |
|
|
|
def _get_header(self): |
|
"""Writes a common header for the generated files.""" |
|
code_lines = [] |
|
code_lines.append("/" + "*"*78 + '\n') |
|
tmp = header_comment % {"version": sympy_version, |
|
"project": self.project} |
|
for line in tmp.splitlines(): |
|
code_lines.append(" *%s*\n" % line.center(76)) |
|
code_lines.append(" " + "*"*78 + "/\n") |
|
return code_lines |
|
|
|
def get_prototype(self, routine): |
|
"""Returns a string for the function prototype of the routine. |
|
|
|
If the routine has multiple result objects, an CodeGenError is |
|
raised. |
|
|
|
See: https://en.wikipedia.org/wiki/Function_prototype |
|
|
|
""" |
|
if len(routine.results) > 1: |
|
raise CodeGenError("C only supports a single or no return value.") |
|
elif len(routine.results) == 1: |
|
ctype = routine.results[0].get_datatype('C') |
|
else: |
|
ctype = "void" |
|
|
|
type_args = [] |
|
for arg in routine.arguments: |
|
name = self.printer.doprint(arg.name) |
|
if arg.dimensions or isinstance(arg, ResultBase): |
|
type_args.append((arg.get_datatype('C'), "*%s" % name)) |
|
else: |
|
type_args.append((arg.get_datatype('C'), name)) |
|
arguments = ", ".join([ "%s %s" % t for t in type_args]) |
|
return "%s %s(%s)" % (ctype, routine.name, arguments) |
|
|
|
def _preprocessor_statements(self, prefix): |
|
code_lines = [] |
|
code_lines.append('#include "{}.h"'.format(os.path.basename(prefix))) |
|
code_lines.extend(self.preprocessor_statements) |
|
code_lines = ['{}\n'.format(l) for l in code_lines] |
|
return code_lines |
|
|
|
def _get_routine_opening(self, routine): |
|
prototype = self.get_prototype(routine) |
|
return ["%s {\n" % prototype] |
|
|
|
def _declare_arguments(self, routine): |
|
|
|
return [] |
|
|
|
def _declare_globals(self, routine): |
|
|
|
return [] |
|
|
|
def _declare_locals(self, routine): |
|
|
|
|
|
|
|
|
|
dereference = [] |
|
for arg in routine.arguments: |
|
if isinstance(arg, ResultBase) and not arg.dimensions: |
|
dereference.append(arg.name) |
|
|
|
code_lines = [] |
|
for result in routine.local_vars: |
|
|
|
|
|
|
|
if not isinstance(result, Result): |
|
continue |
|
|
|
if result.name != result.result_var: |
|
raise CodeGen("Result variable and name should match: {}".format(result)) |
|
assign_to = result.name |
|
t = result.get_datatype('c') |
|
if isinstance(result.expr, (MatrixBase, MatrixExpr)): |
|
dims = result.expr.shape |
|
code_lines.append("{} {}[{}];\n".format(t, str(assign_to), dims[0]*dims[1])) |
|
prefix = "" |
|
else: |
|
prefix = "const {} ".format(t) |
|
|
|
constants, not_c, c_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "dereference": dereference, "strict": False}, |
|
result.expr, assign_to=assign_to) |
|
|
|
for name, value in sorted(constants, key=str): |
|
code_lines.append("double const %s = %s;\n" % (name, value)) |
|
|
|
code_lines.append("{}{}\n".format(prefix, c_expr)) |
|
|
|
return code_lines |
|
|
|
def _call_printer(self, routine): |
|
code_lines = [] |
|
|
|
|
|
|
|
|
|
dereference = [] |
|
for arg in routine.arguments: |
|
if isinstance(arg, ResultBase) and not arg.dimensions: |
|
dereference.append(arg.name) |
|
|
|
return_val = None |
|
for result in routine.result_variables: |
|
if isinstance(result, Result): |
|
assign_to = routine.name + "_result" |
|
t = result.get_datatype('c') |
|
code_lines.append("{} {};\n".format(t, str(assign_to))) |
|
return_val = assign_to |
|
else: |
|
assign_to = result.result_var |
|
|
|
try: |
|
constants, not_c, c_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "dereference": dereference, "strict": False}, |
|
result.expr, assign_to=assign_to) |
|
except AssignmentError: |
|
assign_to = result.result_var |
|
code_lines.append( |
|
"%s %s;\n" % (result.get_datatype('c'), str(assign_to))) |
|
constants, not_c, c_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "dereference": dereference, "strict": False}, |
|
result.expr, assign_to=assign_to) |
|
|
|
for name, value in sorted(constants, key=str): |
|
code_lines.append("double const %s = %s;\n" % (name, value)) |
|
code_lines.append("%s\n" % c_expr) |
|
|
|
if return_val: |
|
code_lines.append(" return %s;\n" % return_val) |
|
return code_lines |
|
|
|
def _get_routine_ending(self, routine): |
|
return ["}\n"] |
|
|
|
def dump_c(self, routines, f, prefix, header=True, empty=True): |
|
self.dump_code(routines, f, prefix, header, empty) |
|
dump_c.extension = code_extension |
|
dump_c.__doc__ = CodeGen.dump_code.__doc__ |
|
|
|
def dump_h(self, routines, f, prefix, header=True, empty=True): |
|
"""Writes the C header file. |
|
|
|
This file contains all the function declarations. |
|
|
|
Parameters |
|
========== |
|
|
|
routines : list |
|
A list of Routine instances. |
|
|
|
f : file-like |
|
Where to write the file. |
|
|
|
prefix : string |
|
The filename prefix, used to construct the include guards. |
|
Only the basename of the prefix is used. |
|
|
|
header : bool, optional |
|
When True, a header comment is included on top of each source |
|
file. [default : True] |
|
|
|
empty : bool, optional |
|
When True, empty lines are included to structure the source |
|
files. [default : True] |
|
|
|
""" |
|
if header: |
|
print(''.join(self._get_header()), file=f) |
|
guard_name = "%s__%s__H" % (self.project.replace( |
|
" ", "_").upper(), prefix.replace("/", "_").upper()) |
|
|
|
if empty: |
|
print(file=f) |
|
print("#ifndef %s" % guard_name, file=f) |
|
print("#define %s" % guard_name, file=f) |
|
if empty: |
|
print(file=f) |
|
|
|
for routine in routines: |
|
prototype = self.get_prototype(routine) |
|
print("%s;" % prototype, file=f) |
|
|
|
if empty: |
|
print(file=f) |
|
print("#endif", file=f) |
|
if empty: |
|
print(file=f) |
|
dump_h.extension = interface_extension |
|
|
|
|
|
|
|
dump_fns = [dump_c, dump_h] |
|
|
|
class C89CodeGen(CCodeGen): |
|
standard = 'C89' |
|
|
|
class C99CodeGen(CCodeGen): |
|
standard = 'C99' |
|
|
|
class FCodeGen(CodeGen): |
|
"""Generator for Fortran 95 code |
|
|
|
The .write() method inherited from CodeGen will output a code file and |
|
an interface file, <prefix>.f90 and <prefix>.h respectively. |
|
|
|
""" |
|
|
|
code_extension = "f90" |
|
interface_extension = "h" |
|
|
|
def __init__(self, project='project', printer=None): |
|
super().__init__(project) |
|
self.printer = printer or FCodePrinter() |
|
|
|
def _get_header(self): |
|
"""Writes a common header for the generated files.""" |
|
code_lines = [] |
|
code_lines.append("!" + "*"*78 + '\n') |
|
tmp = header_comment % {"version": sympy_version, |
|
"project": self.project} |
|
for line in tmp.splitlines(): |
|
code_lines.append("!*%s*\n" % line.center(76)) |
|
code_lines.append("!" + "*"*78 + '\n') |
|
return code_lines |
|
|
|
def _preprocessor_statements(self, prefix): |
|
return [] |
|
|
|
def _get_routine_opening(self, routine): |
|
"""Returns the opening statements of the fortran routine.""" |
|
code_list = [] |
|
if len(routine.results) > 1: |
|
raise CodeGenError( |
|
"Fortran only supports a single or no return value.") |
|
elif len(routine.results) == 1: |
|
result = routine.results[0] |
|
code_list.append(result.get_datatype('fortran')) |
|
code_list.append("function") |
|
else: |
|
code_list.append("subroutine") |
|
|
|
args = ", ".join("%s" % self._get_symbol(arg.name) |
|
for arg in routine.arguments) |
|
|
|
call_sig = "{}({})\n".format(routine.name, args) |
|
|
|
|
|
call_sig = ' &\n'.join(textwrap.wrap(call_sig, |
|
width=60, |
|
break_long_words=False)) + '\n' |
|
code_list.append(call_sig) |
|
code_list = [' '.join(code_list)] |
|
code_list.append('implicit none\n') |
|
return code_list |
|
|
|
def _declare_arguments(self, routine): |
|
|
|
code_list = [] |
|
array_list = [] |
|
scalar_list = [] |
|
for arg in routine.arguments: |
|
|
|
if isinstance(arg, InputArgument): |
|
typeinfo = "%s, intent(in)" % arg.get_datatype('fortran') |
|
elif isinstance(arg, InOutArgument): |
|
typeinfo = "%s, intent(inout)" % arg.get_datatype('fortran') |
|
elif isinstance(arg, OutputArgument): |
|
typeinfo = "%s, intent(out)" % arg.get_datatype('fortran') |
|
else: |
|
raise CodeGenError("Unknown Argument type: %s" % type(arg)) |
|
|
|
fprint = self._get_symbol |
|
|
|
if arg.dimensions: |
|
|
|
dimstr = ", ".join(["%s:%s" % ( |
|
fprint(dim[0] + 1), fprint(dim[1] + 1)) |
|
for dim in arg.dimensions]) |
|
typeinfo += ", dimension(%s)" % dimstr |
|
array_list.append("%s :: %s\n" % (typeinfo, fprint(arg.name))) |
|
else: |
|
scalar_list.append("%s :: %s\n" % (typeinfo, fprint(arg.name))) |
|
|
|
|
|
code_list.extend(scalar_list) |
|
code_list.extend(array_list) |
|
|
|
return code_list |
|
|
|
def _declare_globals(self, routine): |
|
|
|
|
|
return [] |
|
|
|
def _declare_locals(self, routine): |
|
code_list = [] |
|
for var in sorted(routine.local_vars, key=str): |
|
typeinfo = get_default_datatype(var) |
|
code_list.append("%s :: %s\n" % ( |
|
typeinfo.fname, self._get_symbol(var))) |
|
return code_list |
|
|
|
def _get_routine_ending(self, routine): |
|
"""Returns the closing statements of the fortran routine.""" |
|
if len(routine.results) == 1: |
|
return ["end function\n"] |
|
else: |
|
return ["end subroutine\n"] |
|
|
|
def get_interface(self, routine): |
|
"""Returns a string for the function interface. |
|
|
|
The routine should have a single result object, which can be None. |
|
If the routine has multiple result objects, a CodeGenError is |
|
raised. |
|
|
|
See: https://en.wikipedia.org/wiki/Function_prototype |
|
|
|
""" |
|
prototype = [ "interface\n" ] |
|
prototype.extend(self._get_routine_opening(routine)) |
|
prototype.extend(self._declare_arguments(routine)) |
|
prototype.extend(self._get_routine_ending(routine)) |
|
prototype.append("end interface\n") |
|
|
|
return "".join(prototype) |
|
|
|
def _call_printer(self, routine): |
|
declarations = [] |
|
code_lines = [] |
|
for result in routine.result_variables: |
|
if isinstance(result, Result): |
|
assign_to = routine.name |
|
elif isinstance(result, (OutputArgument, InOutArgument)): |
|
assign_to = result.result_var |
|
|
|
constants, not_fortran, f_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "source_format": 'free', "standard": 95, "strict": False}, |
|
result.expr, assign_to=assign_to) |
|
|
|
for obj, v in sorted(constants, key=str): |
|
t = get_default_datatype(obj) |
|
declarations.append( |
|
"%s, parameter :: %s = %s\n" % (t.fname, obj, v)) |
|
for obj in sorted(not_fortran, key=str): |
|
t = get_default_datatype(obj) |
|
if isinstance(obj, Function): |
|
name = obj.func |
|
else: |
|
name = obj |
|
declarations.append("%s :: %s\n" % (t.fname, name)) |
|
|
|
code_lines.append("%s\n" % f_expr) |
|
return declarations + code_lines |
|
|
|
def _indent_code(self, codelines): |
|
return self._printer_method_with_settings( |
|
'indent_code', {"human": False, "source_format": 'free', "strict": False}, codelines) |
|
|
|
def dump_f95(self, routines, f, prefix, header=True, empty=True): |
|
|
|
for r in routines: |
|
lowercase = {str(x).lower() for x in r.variables} |
|
orig_case = {str(x) for x in r.variables} |
|
if len(lowercase) < len(orig_case): |
|
raise CodeGenError("Fortran ignores case. Got symbols: %s" % |
|
(", ".join([str(var) for var in r.variables]))) |
|
self.dump_code(routines, f, prefix, header, empty) |
|
dump_f95.extension = code_extension |
|
dump_f95.__doc__ = CodeGen.dump_code.__doc__ |
|
|
|
def dump_h(self, routines, f, prefix, header=True, empty=True): |
|
"""Writes the interface to a header file. |
|
|
|
This file contains all the function declarations. |
|
|
|
Parameters |
|
========== |
|
|
|
routines : list |
|
A list of Routine instances. |
|
|
|
f : file-like |
|
Where to write the file. |
|
|
|
prefix : string |
|
The filename prefix. |
|
|
|
header : bool, optional |
|
When True, a header comment is included on top of each source |
|
file. [default : True] |
|
|
|
empty : bool, optional |
|
When True, empty lines are included to structure the source |
|
files. [default : True] |
|
|
|
""" |
|
if header: |
|
print(''.join(self._get_header()), file=f) |
|
if empty: |
|
print(file=f) |
|
|
|
for routine in routines: |
|
prototype = self.get_interface(routine) |
|
f.write(prototype) |
|
if empty: |
|
print(file=f) |
|
dump_h.extension = interface_extension |
|
|
|
|
|
|
|
dump_fns = [dump_f95, dump_h] |
|
|
|
|
|
class JuliaCodeGen(CodeGen): |
|
"""Generator for Julia code. |
|
|
|
The .write() method inherited from CodeGen will output a code file |
|
<prefix>.jl. |
|
|
|
""" |
|
|
|
code_extension = "jl" |
|
|
|
def __init__(self, project='project', printer=None): |
|
super().__init__(project) |
|
self.printer = printer or JuliaCodePrinter() |
|
|
|
def routine(self, name, expr, argument_sequence, global_vars): |
|
"""Specialized Routine creation for Julia.""" |
|
|
|
if is_sequence(expr) and not isinstance(expr, (MatrixBase, MatrixExpr)): |
|
if not expr: |
|
raise ValueError("No expression given") |
|
expressions = Tuple(*expr) |
|
else: |
|
expressions = Tuple(expr) |
|
|
|
|
|
local_vars = {i.label for i in expressions.atoms(Idx)} |
|
|
|
|
|
global_vars = set() if global_vars is None else set(global_vars) |
|
|
|
|
|
old_symbols = expressions.free_symbols - local_vars - global_vars |
|
symbols = set() |
|
for s in old_symbols: |
|
if isinstance(s, Idx): |
|
symbols.update(s.args[1].free_symbols) |
|
elif not isinstance(s, Indexed): |
|
symbols.add(s) |
|
|
|
|
|
return_vals = [] |
|
output_args = [] |
|
for (i, expr) in enumerate(expressions): |
|
if isinstance(expr, Equality): |
|
out_arg = expr.lhs |
|
expr = expr.rhs |
|
symbol = out_arg |
|
if isinstance(out_arg, Indexed): |
|
dims = tuple([ (S.One, dim) for dim in out_arg.shape]) |
|
symbol = out_arg.base.label |
|
output_args.append(InOutArgument(symbol, out_arg, expr, dimensions=dims)) |
|
if not isinstance(out_arg, (Indexed, Symbol, MatrixSymbol)): |
|
raise CodeGenError("Only Indexed, Symbol, or MatrixSymbol " |
|
"can define output arguments.") |
|
|
|
return_vals.append(Result(expr, name=symbol, result_var=out_arg)) |
|
if not expr.has(symbol): |
|
|
|
|
|
symbols.remove(symbol) |
|
|
|
else: |
|
|
|
return_vals.append(Result(expr, name='out%d' % (i+1))) |
|
|
|
|
|
output_args.sort(key=lambda x: str(x.name)) |
|
arg_list = list(output_args) |
|
array_symbols = {} |
|
for array in expressions.atoms(Indexed): |
|
array_symbols[array.base.label] = array |
|
for array in expressions.atoms(MatrixSymbol): |
|
array_symbols[array] = array |
|
|
|
for symbol in sorted(symbols, key=str): |
|
arg_list.append(InputArgument(symbol)) |
|
|
|
if argument_sequence is not None: |
|
|
|
new_sequence = [] |
|
for arg in argument_sequence: |
|
if isinstance(arg, IndexedBase): |
|
new_sequence.append(arg.label) |
|
else: |
|
new_sequence.append(arg) |
|
argument_sequence = new_sequence |
|
|
|
missing = [x for x in arg_list if x.name not in argument_sequence] |
|
if missing: |
|
msg = "Argument list didn't specify: {0} " |
|
msg = msg.format(", ".join([str(m.name) for m in missing])) |
|
raise CodeGenArgumentListError(msg, missing) |
|
|
|
|
|
name_arg_dict = {x.name: x for x in arg_list} |
|
new_args = [] |
|
for symbol in argument_sequence: |
|
try: |
|
new_args.append(name_arg_dict[symbol]) |
|
except KeyError: |
|
new_args.append(InputArgument(symbol)) |
|
arg_list = new_args |
|
|
|
return Routine(name, arg_list, return_vals, local_vars, global_vars) |
|
|
|
def _get_header(self): |
|
"""Writes a common header for the generated files.""" |
|
code_lines = [] |
|
tmp = header_comment % {"version": sympy_version, |
|
"project": self.project} |
|
for line in tmp.splitlines(): |
|
if line == '': |
|
code_lines.append("#\n") |
|
else: |
|
code_lines.append("# %s\n" % line) |
|
return code_lines |
|
|
|
def _preprocessor_statements(self, prefix): |
|
return [] |
|
|
|
def _get_routine_opening(self, routine): |
|
"""Returns the opening statements of the routine.""" |
|
code_list = [] |
|
code_list.append("function ") |
|
|
|
|
|
args = [] |
|
for arg in routine.arguments: |
|
if isinstance(arg, OutputArgument): |
|
raise CodeGenError("Julia: invalid argument of type %s" % |
|
str(type(arg))) |
|
if isinstance(arg, (InputArgument, InOutArgument)): |
|
args.append("%s" % self._get_symbol(arg.name)) |
|
args = ", ".join(args) |
|
code_list.append("%s(%s)\n" % (routine.name, args)) |
|
code_list = [ "".join(code_list) ] |
|
|
|
return code_list |
|
|
|
def _declare_arguments(self, routine): |
|
return [] |
|
|
|
def _declare_globals(self, routine): |
|
return [] |
|
|
|
def _declare_locals(self, routine): |
|
return [] |
|
|
|
def _get_routine_ending(self, routine): |
|
outs = [] |
|
for result in routine.results: |
|
if isinstance(result, Result): |
|
|
|
s = self._get_symbol(result.name) |
|
else: |
|
raise CodeGenError("unexpected object in Routine results") |
|
outs.append(s) |
|
return ["return " + ", ".join(outs) + "\nend\n"] |
|
|
|
def _call_printer(self, routine): |
|
declarations = [] |
|
code_lines = [] |
|
for result in routine.results: |
|
if isinstance(result, Result): |
|
assign_to = result.result_var |
|
else: |
|
raise CodeGenError("unexpected object in Routine results") |
|
|
|
constants, not_supported, jl_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "strict": False}, result.expr, assign_to=assign_to) |
|
|
|
for obj, v in sorted(constants, key=str): |
|
declarations.append( |
|
"%s = %s\n" % (obj, v)) |
|
for obj in sorted(not_supported, key=str): |
|
if isinstance(obj, Function): |
|
name = obj.func |
|
else: |
|
name = obj |
|
declarations.append( |
|
"# unsupported: %s\n" % (name)) |
|
code_lines.append("%s\n" % (jl_expr)) |
|
return declarations + code_lines |
|
|
|
def _indent_code(self, codelines): |
|
|
|
|
|
p = JuliaCodePrinter({'human': False, "strict": False}) |
|
return p.indent_code(codelines) |
|
|
|
def dump_jl(self, routines, f, prefix, header=True, empty=True): |
|
self.dump_code(routines, f, prefix, header, empty) |
|
|
|
dump_jl.extension = code_extension |
|
dump_jl.__doc__ = CodeGen.dump_code.__doc__ |
|
|
|
|
|
|
|
dump_fns = [dump_jl] |
|
|
|
|
|
class OctaveCodeGen(CodeGen): |
|
"""Generator for Octave code. |
|
|
|
The .write() method inherited from CodeGen will output a code file |
|
<prefix>.m. |
|
|
|
Octave .m files usually contain one function. That function name should |
|
match the filename (``prefix``). If you pass multiple ``name_expr`` pairs, |
|
the latter ones are presumed to be private functions accessed by the |
|
primary function. |
|
|
|
You should only pass inputs to ``argument_sequence``: outputs are ordered |
|
according to their order in ``name_expr``. |
|
|
|
""" |
|
|
|
code_extension = "m" |
|
|
|
def __init__(self, project='project', printer=None): |
|
super().__init__(project) |
|
self.printer = printer or OctaveCodePrinter() |
|
|
|
def routine(self, name, expr, argument_sequence, global_vars): |
|
"""Specialized Routine creation for Octave.""" |
|
|
|
|
|
|
|
|
|
if is_sequence(expr) and not isinstance(expr, (MatrixBase, MatrixExpr)): |
|
if not expr: |
|
raise ValueError("No expression given") |
|
expressions = Tuple(*expr) |
|
else: |
|
expressions = Tuple(expr) |
|
|
|
|
|
local_vars = {i.label for i in expressions.atoms(Idx)} |
|
|
|
|
|
global_vars = set() if global_vars is None else set(global_vars) |
|
|
|
|
|
old_symbols = expressions.free_symbols - local_vars - global_vars |
|
symbols = set() |
|
for s in old_symbols: |
|
if isinstance(s, Idx): |
|
symbols.update(s.args[1].free_symbols) |
|
elif not isinstance(s, Indexed): |
|
symbols.add(s) |
|
|
|
|
|
return_vals = [] |
|
for (i, expr) in enumerate(expressions): |
|
if isinstance(expr, Equality): |
|
out_arg = expr.lhs |
|
expr = expr.rhs |
|
symbol = out_arg |
|
if isinstance(out_arg, Indexed): |
|
symbol = out_arg.base.label |
|
if not isinstance(out_arg, (Indexed, Symbol, MatrixSymbol)): |
|
raise CodeGenError("Only Indexed, Symbol, or MatrixSymbol " |
|
"can define output arguments.") |
|
|
|
return_vals.append(Result(expr, name=symbol, result_var=out_arg)) |
|
if not expr.has(symbol): |
|
|
|
|
|
symbols.remove(symbol) |
|
|
|
else: |
|
|
|
return_vals.append(Result(expr, name='out%d' % (i+1))) |
|
|
|
|
|
arg_list = [] |
|
array_symbols = {} |
|
for array in expressions.atoms(Indexed): |
|
array_symbols[array.base.label] = array |
|
for array in expressions.atoms(MatrixSymbol): |
|
array_symbols[array] = array |
|
|
|
for symbol in sorted(symbols, key=str): |
|
arg_list.append(InputArgument(symbol)) |
|
|
|
if argument_sequence is not None: |
|
|
|
new_sequence = [] |
|
for arg in argument_sequence: |
|
if isinstance(arg, IndexedBase): |
|
new_sequence.append(arg.label) |
|
else: |
|
new_sequence.append(arg) |
|
argument_sequence = new_sequence |
|
|
|
missing = [x for x in arg_list if x.name not in argument_sequence] |
|
if missing: |
|
msg = "Argument list didn't specify: {0} " |
|
msg = msg.format(", ".join([str(m.name) for m in missing])) |
|
raise CodeGenArgumentListError(msg, missing) |
|
|
|
|
|
name_arg_dict = {x.name: x for x in arg_list} |
|
new_args = [] |
|
for symbol in argument_sequence: |
|
try: |
|
new_args.append(name_arg_dict[symbol]) |
|
except KeyError: |
|
new_args.append(InputArgument(symbol)) |
|
arg_list = new_args |
|
|
|
return Routine(name, arg_list, return_vals, local_vars, global_vars) |
|
|
|
def _get_header(self): |
|
"""Writes a common header for the generated files.""" |
|
code_lines = [] |
|
tmp = header_comment % {"version": sympy_version, |
|
"project": self.project} |
|
for line in tmp.splitlines(): |
|
if line == '': |
|
code_lines.append("%\n") |
|
else: |
|
code_lines.append("%% %s\n" % line) |
|
return code_lines |
|
|
|
def _preprocessor_statements(self, prefix): |
|
return [] |
|
|
|
def _get_routine_opening(self, routine): |
|
"""Returns the opening statements of the routine.""" |
|
code_list = [] |
|
code_list.append("function ") |
|
|
|
|
|
outs = [] |
|
for result in routine.results: |
|
if isinstance(result, Result): |
|
|
|
s = self._get_symbol(result.name) |
|
else: |
|
raise CodeGenError("unexpected object in Routine results") |
|
outs.append(s) |
|
if len(outs) > 1: |
|
code_list.append("[" + (", ".join(outs)) + "]") |
|
else: |
|
code_list.append("".join(outs)) |
|
code_list.append(" = ") |
|
|
|
|
|
args = [] |
|
for arg in routine.arguments: |
|
if isinstance(arg, (OutputArgument, InOutArgument)): |
|
raise CodeGenError("Octave: invalid argument of type %s" % |
|
str(type(arg))) |
|
if isinstance(arg, InputArgument): |
|
args.append("%s" % self._get_symbol(arg.name)) |
|
args = ", ".join(args) |
|
code_list.append("%s(%s)\n" % (routine.name, args)) |
|
code_list = [ "".join(code_list) ] |
|
|
|
return code_list |
|
|
|
def _declare_arguments(self, routine): |
|
return [] |
|
|
|
def _declare_globals(self, routine): |
|
if not routine.global_vars: |
|
return [] |
|
s = " ".join(sorted([self._get_symbol(g) for g in routine.global_vars])) |
|
return ["global " + s + "\n"] |
|
|
|
def _declare_locals(self, routine): |
|
return [] |
|
|
|
def _get_routine_ending(self, routine): |
|
return ["end\n"] |
|
|
|
def _call_printer(self, routine): |
|
declarations = [] |
|
code_lines = [] |
|
for result in routine.results: |
|
if isinstance(result, Result): |
|
assign_to = result.result_var |
|
else: |
|
raise CodeGenError("unexpected object in Routine results") |
|
|
|
constants, not_supported, oct_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "strict": False}, result.expr, assign_to=assign_to) |
|
|
|
for obj, v in sorted(constants, key=str): |
|
declarations.append( |
|
" %s = %s; %% constant\n" % (obj, v)) |
|
for obj in sorted(not_supported, key=str): |
|
if isinstance(obj, Function): |
|
name = obj.func |
|
else: |
|
name = obj |
|
declarations.append( |
|
" %% unsupported: %s\n" % (name)) |
|
code_lines.append("%s\n" % (oct_expr)) |
|
return declarations + code_lines |
|
|
|
def _indent_code(self, codelines): |
|
return self._printer_method_with_settings( |
|
'indent_code', {"human": False, "strict": False}, codelines) |
|
|
|
def dump_m(self, routines, f, prefix, header=True, empty=True, inline=True): |
|
|
|
|
|
code_lines = self._preprocessor_statements(prefix) |
|
|
|
for i, routine in enumerate(routines): |
|
if i > 0: |
|
if empty: |
|
code_lines.append("\n") |
|
code_lines.extend(self._get_routine_opening(routine)) |
|
if i == 0: |
|
if routine.name != prefix: |
|
raise ValueError('Octave function name should match prefix') |
|
if header: |
|
code_lines.append("%" + prefix.upper() + |
|
" Autogenerated by SymPy\n") |
|
code_lines.append(''.join(self._get_header())) |
|
code_lines.extend(self._declare_arguments(routine)) |
|
code_lines.extend(self._declare_globals(routine)) |
|
code_lines.extend(self._declare_locals(routine)) |
|
if empty: |
|
code_lines.append("\n") |
|
code_lines.extend(self._call_printer(routine)) |
|
if empty: |
|
code_lines.append("\n") |
|
code_lines.extend(self._get_routine_ending(routine)) |
|
|
|
code_lines = self._indent_code(''.join(code_lines)) |
|
|
|
if code_lines: |
|
f.write(code_lines) |
|
|
|
dump_m.extension = code_extension |
|
dump_m.__doc__ = CodeGen.dump_code.__doc__ |
|
|
|
|
|
|
|
dump_fns = [dump_m] |
|
|
|
class RustCodeGen(CodeGen): |
|
"""Generator for Rust code. |
|
|
|
The .write() method inherited from CodeGen will output a code file |
|
<prefix>.rs |
|
|
|
""" |
|
|
|
code_extension = "rs" |
|
|
|
def __init__(self, project="project", printer=None): |
|
super().__init__(project=project) |
|
self.printer = printer or RustCodePrinter() |
|
|
|
def routine(self, name, expr, argument_sequence, global_vars): |
|
"""Specialized Routine creation for Rust.""" |
|
|
|
if is_sequence(expr) and not isinstance(expr, (MatrixBase, MatrixExpr)): |
|
if not expr: |
|
raise ValueError("No expression given") |
|
expressions = Tuple(*expr) |
|
else: |
|
expressions = Tuple(expr) |
|
|
|
|
|
local_vars = {i.label for i in expressions.atoms(Idx)} |
|
|
|
|
|
global_vars = set() if global_vars is None else set(global_vars) |
|
|
|
|
|
symbols = expressions.free_symbols - local_vars - global_vars - expressions.atoms(Indexed) |
|
|
|
|
|
return_vals = [] |
|
output_args = [] |
|
for (i, expr) in enumerate(expressions): |
|
if isinstance(expr, Equality): |
|
out_arg = expr.lhs |
|
expr = expr.rhs |
|
symbol = out_arg |
|
if isinstance(out_arg, Indexed): |
|
dims = tuple([ (S.One, dim) for dim in out_arg.shape]) |
|
symbol = out_arg.base.label |
|
output_args.append(InOutArgument(symbol, out_arg, expr, dimensions=dims)) |
|
if not isinstance(out_arg, (Indexed, Symbol, MatrixSymbol)): |
|
raise CodeGenError("Only Indexed, Symbol, or MatrixSymbol " |
|
"can define output arguments.") |
|
|
|
return_vals.append(Result(expr, name=symbol, result_var=out_arg)) |
|
if not expr.has(symbol): |
|
|
|
|
|
symbols.remove(symbol) |
|
|
|
else: |
|
|
|
return_vals.append(Result(expr, name='out%d' % (i+1))) |
|
|
|
|
|
output_args.sort(key=lambda x: str(x.name)) |
|
arg_list = list(output_args) |
|
array_symbols = {} |
|
for array in expressions.atoms(Indexed): |
|
array_symbols[array.base.label] = array |
|
for array in expressions.atoms(MatrixSymbol): |
|
array_symbols[array] = array |
|
|
|
for symbol in sorted(symbols, key=str): |
|
arg_list.append(InputArgument(symbol)) |
|
|
|
if argument_sequence is not None: |
|
|
|
new_sequence = [] |
|
for arg in argument_sequence: |
|
if isinstance(arg, IndexedBase): |
|
new_sequence.append(arg.label) |
|
else: |
|
new_sequence.append(arg) |
|
argument_sequence = new_sequence |
|
|
|
missing = [x for x in arg_list if x.name not in argument_sequence] |
|
if missing: |
|
msg = "Argument list didn't specify: {0} " |
|
msg = msg.format(", ".join([str(m.name) for m in missing])) |
|
raise CodeGenArgumentListError(msg, missing) |
|
|
|
|
|
name_arg_dict = {x.name: x for x in arg_list} |
|
new_args = [] |
|
for symbol in argument_sequence: |
|
try: |
|
new_args.append(name_arg_dict[symbol]) |
|
except KeyError: |
|
new_args.append(InputArgument(symbol)) |
|
arg_list = new_args |
|
|
|
return Routine(name, arg_list, return_vals, local_vars, global_vars) |
|
|
|
|
|
def _get_header(self): |
|
"""Writes a common header for the generated files.""" |
|
code_lines = [] |
|
code_lines.append("/*\n") |
|
tmp = header_comment % {"version": sympy_version, |
|
"project": self.project} |
|
for line in tmp.splitlines(): |
|
code_lines.append((" *%s" % line.center(76)).rstrip() + "\n") |
|
code_lines.append(" */\n") |
|
return code_lines |
|
|
|
def get_prototype(self, routine): |
|
"""Returns a string for the function prototype of the routine. |
|
|
|
If the routine has multiple result objects, an CodeGenError is |
|
raised. |
|
|
|
See: https://en.wikipedia.org/wiki/Function_prototype |
|
|
|
""" |
|
results = [i.get_datatype('Rust') for i in routine.results] |
|
|
|
if len(results) == 1: |
|
rstype = " -> " + results[0] |
|
elif len(routine.results) > 1: |
|
rstype = " -> (" + ", ".join(results) + ")" |
|
else: |
|
rstype = "" |
|
|
|
type_args = [] |
|
for arg in routine.arguments: |
|
name = self.printer.doprint(arg.name) |
|
if arg.dimensions or isinstance(arg, ResultBase): |
|
type_args.append(("*%s" % name, arg.get_datatype('Rust'))) |
|
else: |
|
type_args.append((name, arg.get_datatype('Rust'))) |
|
arguments = ", ".join([ "%s: %s" % t for t in type_args]) |
|
return "fn %s(%s)%s" % (routine.name, arguments, rstype) |
|
|
|
def _preprocessor_statements(self, prefix): |
|
code_lines = [] |
|
|
|
return code_lines |
|
|
|
def _get_routine_opening(self, routine): |
|
prototype = self.get_prototype(routine) |
|
return ["%s {\n" % prototype] |
|
|
|
def _declare_arguments(self, routine): |
|
|
|
return [] |
|
|
|
def _declare_globals(self, routine): |
|
|
|
return [] |
|
|
|
def _declare_locals(self, routine): |
|
|
|
return [] |
|
|
|
def _call_printer(self, routine): |
|
|
|
code_lines = [] |
|
declarations = [] |
|
returns = [] |
|
|
|
|
|
|
|
|
|
dereference = [] |
|
for arg in routine.arguments: |
|
if isinstance(arg, ResultBase) and not arg.dimensions: |
|
dereference.append(arg.name) |
|
|
|
for result in routine.results: |
|
if isinstance(result, Result): |
|
assign_to = result.result_var |
|
returns.append(str(result.result_var)) |
|
else: |
|
raise CodeGenError("unexpected object in Routine results") |
|
|
|
constants, not_supported, rs_expr = self._printer_method_with_settings( |
|
'doprint', {"human": False, "strict": False}, result.expr, assign_to=assign_to) |
|
|
|
for name, value in sorted(constants, key=str): |
|
declarations.append("const %s: f64 = %s;\n" % (name, value)) |
|
|
|
for obj in sorted(not_supported, key=str): |
|
if isinstance(obj, Function): |
|
name = obj.func |
|
else: |
|
name = obj |
|
declarations.append("// unsupported: %s\n" % (name)) |
|
|
|
code_lines.append("let %s\n" % rs_expr); |
|
|
|
if len(returns) > 1: |
|
returns = ['(' + ', '.join(returns) + ')'] |
|
|
|
returns.append('\n') |
|
|
|
return declarations + code_lines + returns |
|
|
|
def _get_routine_ending(self, routine): |
|
return ["}\n"] |
|
|
|
def dump_rs(self, routines, f, prefix, header=True, empty=True): |
|
self.dump_code(routines, f, prefix, header, empty) |
|
|
|
dump_rs.extension = code_extension |
|
dump_rs.__doc__ = CodeGen.dump_code.__doc__ |
|
|
|
|
|
|
|
dump_fns = [dump_rs] |
|
|
|
|
|
|
|
|
|
def get_code_generator(language, project=None, standard=None, printer = None): |
|
if language == 'C': |
|
if standard is None: |
|
pass |
|
elif standard.lower() == 'c89': |
|
language = 'C89' |
|
elif standard.lower() == 'c99': |
|
language = 'C99' |
|
CodeGenClass = {"C": CCodeGen, "C89": C89CodeGen, "C99": C99CodeGen, |
|
"F95": FCodeGen, "JULIA": JuliaCodeGen, |
|
"OCTAVE": OctaveCodeGen, |
|
"RUST": RustCodeGen}.get(language.upper()) |
|
if CodeGenClass is None: |
|
raise ValueError("Language '%s' is not supported." % language) |
|
return CodeGenClass(project, printer) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def codegen(name_expr, language=None, prefix=None, project="project", |
|
to_files=False, header=True, empty=True, argument_sequence=None, |
|
global_vars=None, standard=None, code_gen=None, printer=None): |
|
"""Generate source code for expressions in a given language. |
|
|
|
Parameters |
|
========== |
|
|
|
name_expr : tuple, or list of tuples |
|
A single (name, expression) tuple or a list of (name, expression) |
|
tuples. Each tuple corresponds to a routine. If the expression is |
|
an equality (an instance of class Equality) the left hand side is |
|
considered an output argument. If expression is an iterable, then |
|
the routine will have multiple outputs. |
|
|
|
language : string, |
|
A string that indicates the source code language. This is case |
|
insensitive. Currently, 'C', 'F95' and 'Octave' are supported. |
|
'Octave' generates code compatible with both Octave and Matlab. |
|
|
|
prefix : string, optional |
|
A prefix for the names of the files that contain the source code. |
|
Language-dependent suffixes will be appended. If omitted, the name |
|
of the first name_expr tuple is used. |
|
|
|
project : string, optional |
|
A project name, used for making unique preprocessor instructions. |
|
[default: "project"] |
|
|
|
to_files : bool, optional |
|
When True, the code will be written to one or more files with the |
|
given prefix, otherwise strings with the names and contents of |
|
these files are returned. [default: False] |
|
|
|
header : bool, optional |
|
When True, a header is written on top of each source file. |
|
[default: True] |
|
|
|
empty : bool, optional |
|
When True, empty lines are used to structure the code. |
|
[default: True] |
|
|
|
argument_sequence : iterable, optional |
|
Sequence of arguments for the routine in a preferred order. A |
|
CodeGenError is raised if required arguments are missing. |
|
Redundant arguments are used without warning. If omitted, |
|
arguments will be ordered alphabetically, but with all input |
|
arguments first, and then output or in-out arguments. |
|
|
|
global_vars : iterable, optional |
|
Sequence of global variables used by the routine. Variables |
|
listed here will not show up as function arguments. |
|
|
|
standard : string, optional |
|
|
|
code_gen : CodeGen instance, optional |
|
An instance of a CodeGen subclass. Overrides ``language``. |
|
|
|
printer : Printer instance, optional |
|
An instance of a Printer subclass. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.codegen import codegen |
|
>>> from sympy.abc import x, y, z |
|
>>> [(c_name, c_code), (h_name, c_header)] = codegen( |
|
... ("f", x+y*z), "C89", "test", header=False, empty=False) |
|
>>> print(c_name) |
|
test.c |
|
>>> print(c_code) |
|
#include "test.h" |
|
#include <math.h> |
|
double f(double x, double y, double z) { |
|
double f_result; |
|
f_result = x + y*z; |
|
return f_result; |
|
} |
|
<BLANKLINE> |
|
>>> print(h_name) |
|
test.h |
|
>>> print(c_header) |
|
#ifndef PROJECT__TEST__H |
|
#define PROJECT__TEST__H |
|
double f(double x, double y, double z); |
|
#endif |
|
<BLANKLINE> |
|
|
|
Another example using Equality objects to give named outputs. Here the |
|
filename (prefix) is taken from the first (name, expr) pair. |
|
|
|
>>> from sympy.abc import f, g |
|
>>> from sympy import Eq |
|
>>> [(c_name, c_code), (h_name, c_header)] = codegen( |
|
... [("myfcn", x + y), ("fcn2", [Eq(f, 2*x), Eq(g, y)])], |
|
... "C99", header=False, empty=False) |
|
>>> print(c_name) |
|
myfcn.c |
|
>>> print(c_code) |
|
#include "myfcn.h" |
|
#include <math.h> |
|
double myfcn(double x, double y) { |
|
double myfcn_result; |
|
myfcn_result = x + y; |
|
return myfcn_result; |
|
} |
|
void fcn2(double x, double y, double *f, double *g) { |
|
(*f) = 2*x; |
|
(*g) = y; |
|
} |
|
<BLANKLINE> |
|
|
|
If the generated function(s) will be part of a larger project where various |
|
global variables have been defined, the 'global_vars' option can be used |
|
to remove the specified variables from the function signature |
|
|
|
>>> from sympy.utilities.codegen import codegen |
|
>>> from sympy.abc import x, y, z |
|
>>> [(f_name, f_code), header] = codegen( |
|
... ("f", x+y*z), "F95", header=False, empty=False, |
|
... argument_sequence=(x, y), global_vars=(z,)) |
|
>>> print(f_code) |
|
REAL*8 function f(x, y) |
|
implicit none |
|
REAL*8, intent(in) :: x |
|
REAL*8, intent(in) :: y |
|
f = x + y*z |
|
end function |
|
<BLANKLINE> |
|
|
|
""" |
|
|
|
|
|
if language is None: |
|
if code_gen is None: |
|
raise ValueError("Need either language or code_gen") |
|
else: |
|
if code_gen is not None: |
|
raise ValueError("You cannot specify both language and code_gen.") |
|
code_gen = get_code_generator(language, project, standard, printer) |
|
|
|
if isinstance(name_expr[0], str): |
|
|
|
name_expr = [name_expr] |
|
|
|
if prefix is None: |
|
prefix = name_expr[0][0] |
|
|
|
|
|
routines = [] |
|
for name, expr in name_expr: |
|
routines.append(code_gen.routine(name, expr, argument_sequence, |
|
global_vars)) |
|
|
|
|
|
return code_gen.write(routines, prefix, to_files, header, empty) |
|
|
|
|
|
def make_routine(name, expr, argument_sequence=None, |
|
global_vars=None, language="F95"): |
|
"""A factory that makes an appropriate Routine from an expression. |
|
|
|
Parameters |
|
========== |
|
|
|
name : string |
|
The name of this routine in the generated code. |
|
|
|
expr : expression or list/tuple of expressions |
|
A SymPy expression that the Routine instance will represent. If |
|
given a list or tuple of expressions, the routine will be |
|
considered to have multiple return values and/or output arguments. |
|
|
|
argument_sequence : list or tuple, optional |
|
List arguments for the routine in a preferred order. If omitted, |
|
the results are language dependent, for example, alphabetical order |
|
or in the same order as the given expressions. |
|
|
|
global_vars : iterable, optional |
|
Sequence of global variables used by the routine. Variables |
|
listed here will not show up as function arguments. |
|
|
|
language : string, optional |
|
Specify a target language. The Routine itself should be |
|
language-agnostic but the precise way one is created, error |
|
checking, etc depend on the language. [default: "F95"]. |
|
|
|
Notes |
|
===== |
|
|
|
A decision about whether to use output arguments or return values is made |
|
depending on both the language and the particular mathematical expressions. |
|
For an expression of type Equality, the left hand side is typically made |
|
into an OutputArgument (or perhaps an InOutArgument if appropriate). |
|
Otherwise, typically, the calculated expression is made a return values of |
|
the routine. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.codegen import make_routine |
|
>>> from sympy.abc import x, y, f, g |
|
>>> from sympy import Eq |
|
>>> r = make_routine('test', [Eq(f, 2*x), Eq(g, x + y)]) |
|
>>> [arg.result_var for arg in r.results] |
|
[] |
|
>>> [arg.name for arg in r.arguments] |
|
[x, y, f, g] |
|
>>> [arg.name for arg in r.result_variables] |
|
[f, g] |
|
>>> r.local_vars |
|
set() |
|
|
|
Another more complicated example with a mixture of specified and |
|
automatically-assigned names. Also has Matrix output. |
|
|
|
>>> from sympy import Matrix |
|
>>> r = make_routine('fcn', [x*y, Eq(f, 1), Eq(g, x + g), Matrix([[x, 2]])]) |
|
>>> [arg.result_var for arg in r.results] # doctest: +SKIP |
|
[result_5397460570204848505] |
|
>>> [arg.expr for arg in r.results] |
|
[x*y] |
|
>>> [arg.name for arg in r.arguments] # doctest: +SKIP |
|
[x, y, f, g, out_8598435338387848786] |
|
|
|
We can examine the various arguments more closely: |
|
|
|
>>> from sympy.utilities.codegen import (InputArgument, OutputArgument, |
|
... InOutArgument) |
|
>>> [a.name for a in r.arguments if isinstance(a, InputArgument)] |
|
[x, y] |
|
|
|
>>> [a.name for a in r.arguments if isinstance(a, OutputArgument)] # doctest: +SKIP |
|
[f, out_8598435338387848786] |
|
>>> [a.expr for a in r.arguments if isinstance(a, OutputArgument)] |
|
[1, Matrix([[x, 2]])] |
|
|
|
>>> [a.name for a in r.arguments if isinstance(a, InOutArgument)] |
|
[g] |
|
>>> [a.expr for a in r.arguments if isinstance(a, InOutArgument)] |
|
[g + x] |
|
|
|
""" |
|
|
|
|
|
code_gen = get_code_generator(language) |
|
|
|
return code_gen.routine(name, expr, argument_sequence, global_vars) |
|
|