| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237 |
- """
- 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__ = [
- # description of routines
- "Routine", "DataType", "default_datatypes", "get_default_datatype",
- "Argument", "InputArgument", "OutputArgument", "Result",
- # routines -> code
- "CodeGen", "CCodeGen", "FCodeGen", "JuliaCodeGen", "OctaveCodeGen",
- "RustCodeGen",
- # friendly functions
- "codegen", "make_routine",
- ]
- #
- # Description of routines
- #
- 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.
- """
- # extract all input symbols and all symbols appearing in an expression
- 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}
- # Check that all symbols in the expressions are covered by
- # InputArguments/InOutArguments---subset because user could
- # specify additional (unused) InputArguments or local_vars.
- 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") #FIXME:
- # complex is only supported in fortran, python, julia, and octave.
- # So to not break c or rust code generation, we stick with double or
- # float, respectively (but actually should raise an exception for
- # explicitly complex variables (x.is_complex==True))
- }
- 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):
- #check all entries
- 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.
- """
- # Basic because it is the base class for all types of expressions
- 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:
- #try to infer data type from the expression
- 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__
- #
- # Transformation of routine objects into code
- #
- class CodeGen:
- """Abstract class for the code generators."""
- printer = None # will be set to an instance of a CodePrinter subclass
- 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))
- # create a list of right hand sides and simplify them
- rhs = [e.rhs for e in expr]
- common, simplified = cse(rhs)
- # pack the simplified expressions back up with their left hand sides
- expr = [Equality(e.lhs, rhs) for e, rhs in zip(expr, simplified)]
- else:
- if isinstance(expr, Equality):
- common, simplified = cse(expr.rhs) #, ignore=in_out_args)
- 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 variables for indexed expressions
- local_vars = {i.label for i in expressions.atoms(Idx)}
- local_symbols = local_vars
- # global variables
- global_vars = set() if global_vars is None else set(global_vars)
- # symbols that should be arguments
- 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
- # Decide whether to use output argument or return value
- 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))
- # remove duplicate arguments when they are not local variables
- if symbol not in local_vars:
- # avoid duplicate arguments
- symbols.remove(symbol)
- elif isinstance(expr, (ImmutableMatrix, MatrixSlice)):
- # Create a "dummy" MatrixSymbol to use as the Output arg
- 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 = []
- # setup input argument list
- # helper to get dimensions for data for array-like args
- 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:
- # if the user has supplied IndexedBase instances, we'll accept that
- 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)
- # create redundant arguments to produce the requested sequence
- 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):
- # arguments are declared in prototype
- return []
- def _declare_globals(self, routine):
- # global variables are not explicitly declared within C functions
- return []
- def _declare_locals(self, routine):
- # Compose a list of symbols to be dereferenced in the function
- # body. These are the arguments that were passed by a reference
- # pointer, excluding arrays.
- 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:
- # local variables that are simple symbols such as those used as indices into
- # for loops are defined declared elsewhere.
- 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 = []
- # Compose a list of symbols to be dereferenced in the function
- # body. These are the arguments that were passed by a reference
- # pointer, excluding arrays.
- 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 # type: ignore
- 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())
- # include guards
- if empty:
- print(file=f)
- print("#ifndef %s" % guard_name, file=f)
- print("#define %s" % guard_name, file=f)
- if empty:
- print(file=f)
- # declaration of the function prototypes
- for routine in routines:
- prototype = self.get_prototype(routine)
- print("%s;" % prototype, file=f)
- # end if include guards
- if empty:
- print(file=f)
- print("#endif", file=f)
- if empty:
- print(file=f)
- dump_h.extension = interface_extension # type: ignore
- # This list of dump functions is used by CodeGen.write to know which dump
- # functions it has to call.
- 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)
- # Fortran 95 requires all lines be less than 132 characters, so wrap
- # this line before appending.
- 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):
- # argument type declarations
- 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:
- # fortran arrays start at 1
- 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)))
- # scalars first, because they can be used in array declarations
- code_list.extend(scalar_list)
- code_list.extend(array_list)
- return code_list
- def _declare_globals(self, routine):
- # Global variables not explicitly declared within Fortran 90 functions.
- # Note: a future F77 mode may need to generate "common" blocks.
- 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):
- # check that symbols are unique with ignorecase
- 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 # type: ignore
- 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)
- # declaration of the function prototypes
- for routine in routines:
- prototype = self.get_interface(routine)
- f.write(prototype)
- if empty:
- print(file=f)
- dump_h.extension = interface_extension # type: ignore
- # This list of dump functions is used by CodeGen.write to know which dump
- # functions it has to call.
- 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 variables
- local_vars = {i.label for i in expressions.atoms(Idx)}
- # global variables
- global_vars = set() if global_vars is None else set(global_vars)
- # symbols that should be arguments
- 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)
- # Julia supports multiple return values
- 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):
- # this is a pure output: remove from the symbols list, so
- # it doesn't become an input.
- symbols.remove(symbol)
- else:
- # we have no name for this output
- return_vals.append(Result(expr, name='out%d' % (i+1)))
- # setup input argument list
- 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:
- # if the user has supplied IndexedBase instances, we'll accept that
- 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)
- # create redundant arguments to produce the requested sequence
- 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 ")
- # Inputs
- 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):
- # Note: name not result_var; want `y` not `y[i]` for Indexed
- 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):
- # Note that indenting seems to happen twice, first
- # statement-by-statement by JuliaPrinter then again here.
- 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 # type: ignore
- dump_jl.__doc__ = CodeGen.dump_code.__doc__
- # This list of dump functions is used by CodeGen.write to know which dump
- # functions it has to call.
- 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."""
- # FIXME: this is probably general enough for other high-level
- # languages, perhaps its the C/Fortran one that is specialized!
- 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 variables
- local_vars = {i.label for i in expressions.atoms(Idx)}
- # global variables
- global_vars = set() if global_vars is None else set(global_vars)
- # symbols that should be arguments
- 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)
- # Octave supports multiple return values
- 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):
- # this is a pure output: remove from the symbols list, so
- # it doesn't become an input.
- symbols.remove(symbol)
- else:
- # we have no name for this output
- return_vals.append(Result(expr, name='out%d' % (i+1)))
- # setup input argument list
- 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:
- # if the user has supplied IndexedBase instances, we'll accept that
- 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)
- # create redundant arguments to produce the requested sequence
- 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 ")
- # Outputs
- outs = []
- for result in routine.results:
- if isinstance(result, Result):
- # Note: name not result_var; want `y` not `y(i)` for Indexed
- 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(" = ")
- # Inputs
- 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):
- # Note used to call self.dump_code() but we need more control for header
- 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 # type: ignore
- dump_m.__doc__ = CodeGen.dump_code.__doc__
- # This list of dump functions is used by CodeGen.write to know which dump
- # functions it has to call.
- 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 variables
- local_vars = {i.label for i in expressions.atoms(Idx)}
- # global variables
- global_vars = set() if global_vars is None else set(global_vars)
- # symbols that should be arguments
- symbols = expressions.free_symbols - local_vars - global_vars - expressions.atoms(Indexed)
- # Rust supports multiple return values
- 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):
- # this is a pure output: remove from the symbols list, so
- # it doesn't become an input.
- symbols.remove(symbol)
- else:
- # we have no name for this output
- return_vals.append(Result(expr, name='out%d' % (i+1)))
- # setup input argument list
- 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:
- # if the user has supplied IndexedBase instances, we'll accept that
- 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)
- # create redundant arguments to produce the requested sequence
- 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 = []
- # code_lines.append("use std::f64::consts::*;\n")
- return code_lines
- def _get_routine_opening(self, routine):
- prototype = self.get_prototype(routine)
- return ["%s {\n" % prototype]
- def _declare_arguments(self, routine):
- # arguments are declared in prototype
- return []
- def _declare_globals(self, routine):
- # global variables are not explicitly declared within C functions
- return []
- def _declare_locals(self, routine):
- # loop variables are declared in loop statement
- return []
- def _call_printer(self, routine):
- code_lines = []
- declarations = []
- returns = []
- # Compose a list of symbols to be dereferenced in the function
- # body. These are the arguments that were passed by a reference
- # pointer, excluding arrays.
- 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 # type: ignore
- dump_rs.__doc__ = CodeGen.dump_code.__doc__
- # This list of dump functions is used by CodeGen.write to know which dump
- # functions it has to call.
- 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)
- #
- # Friendly functions
- #
- 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>
- """
- # Initialize the code generator.
- 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):
- # single tuple is given, turn it into a singleton list with a tuple.
- name_expr = [name_expr]
- if prefix is None:
- prefix = name_expr[0][0]
- # Construct Routines appropriate for this code_gen from (name, expr) pairs.
- routines = []
- for name, expr in name_expr:
- routines.append(code_gen.routine(name, expr, argument_sequence,
- global_vars))
- # Write the code.
- 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]
- """
- # initialize a new code generator
- code_gen = get_code_generator(language)
- return code_gen.routine(name, expr, argument_sequence, global_vars)
|