1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236 |
- """
- 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)
- for arg in self.arguments:
- v.add(arg.name)
- for res in self.results:
- v.add(res.result_var)
- 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 ot 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, respecitvely (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.
- dimension : sequence containing tupes, 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 dimension 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.
- dimension : sequence containing tupes, 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.
- dimension : sequence containing tupes, 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', dict(human=False, dereference=dereference),
- 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', dict(human=False, dereference=dereference),
- 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', dict(human=False, dereference=dereference),
- 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', dict(human=False, source_format='free', standard=95),
- 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', dict(human=False, source_format='free'), 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 i, arg in enumerate(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 i, result in enumerate(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', dict(human=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})
- 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 i, result in enumerate(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 i, arg in enumerate(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 i, result in enumerate(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', dict(human=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', dict(human=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 i, result in enumerate(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', dict(human=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
- code_gen : CodeGen instance
- An instance of a CodeGen subclass. Overrides ``language``.
- 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)
|