diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index b96d559a1733ec69b6f40db04f41437cc80e3ad7..db671b9abeecd2175e05446041fb843e2c547501 100644 --- a/src/pystencilssfg/composer/basic_composer.py +++ b/src/pystencilssfg/composer/basic_composer.py @@ -4,6 +4,7 @@ from abc import ABC, abstractmethod import numpy as np import sympy as sp from functools import reduce +from warnings import warn from pystencils import Field from pystencils.codegen import Kernel @@ -252,7 +253,13 @@ class SfgBasicComposer(SfgIComposer): func = SfgFunction(name, tree) self._ctx.add_function(func) - def function(self, name: str, return_type: UserTypeSpec = void): + def function( + self, + name: str, + returns: UserTypeSpec = void, + inline: bool = False, + return_type: UserTypeSpec | None = None, + ): """Add a function. The syntax of this function adder uses a chain of two calls to mimic C++ syntax: @@ -265,12 +272,23 @@ class SfgBasicComposer(SfgIComposer): The function body is constructed via sequencing (see `make_sequence`). """ + if return_type is not None: + warn( + "The parameter `return_type` to `function()` is deprecated and will be removed by version 0.1. " + "Setting it will override the value of the `returns` parameter. " + "Use `returns` instead.", + FutureWarning, + ) + returns = return_type + if self._ctx.get_function(name) is not None: raise ValueError(f"Function {name} already exists.") def sequencer(*args: SequencerArg): tree = make_sequence(*args) - func = SfgFunction(name, tree, return_type=create_type(return_type)) + func = SfgFunction( + name, tree, return_type=create_type(returns), inline=inline + ) self._ctx.add_function(func) return sequencer diff --git a/src/pystencilssfg/ir/postprocessing.py b/src/pystencilssfg/ir/postprocessing.py index aa3cd2732f62f5b9b50131b4e1ae1b48aa23e4ce..db26a38ba2ffd189edb91156e6f0dc95cf29c616 100644 --- a/src/pystencilssfg/ir/postprocessing.py +++ b/src/pystencilssfg/ir/postprocessing.py @@ -29,7 +29,6 @@ from ..lang import ( if TYPE_CHECKING: from ..context import SfgContext - from .source_components import SfgClass class FlattenSequences: @@ -65,19 +64,9 @@ class FlattenSequences: class PostProcessingContext: - def __init__(self, enclosing_class: SfgClass | None = None) -> None: - self.enclosing_class: SfgClass | None = enclosing_class + def __init__(self) -> None: self._live_variables: dict[str, SfgVar] = dict() - def is_method(self) -> bool: - return self.enclosing_class is not None - - def get_enclosing_class(self) -> SfgClass: - if self.enclosing_class is None: - raise SfgException("Cannot get the enclosing class of a free function.") - - return self.enclosing_class - @property def live_variables(self) -> set[SfgVar]: return set(self._live_variables.values()) @@ -144,8 +133,7 @@ class PostProcessingResult: class CallTreePostProcessing: - def __init__(self, enclosing_class: SfgClass | None = None): - self._enclosing_class = enclosing_class + def __init__(self): self._flattener = FlattenSequences() def __call__(self, ast: SfgCallTreeNode) -> PostProcessingResult: @@ -174,7 +162,7 @@ class CallTreePostProcessing: def get_live_variables(self, node: SfgCallTreeNode) -> set[SfgVar]: match node: case SfgSequence(): - ppc = self._ppc() + ppc = PostProcessingContext() self.handle_sequence(node, ppc) return ppc.live_variables @@ -191,9 +179,6 @@ class CallTreePostProcessing: set(), ) - def _ppc(self) -> PostProcessingContext: - return PostProcessingContext(enclosing_class=self._enclosing_class) - class SfgDeferredNode(SfgCallTreeNode, ABC): """Nodes of this type are inserted as placeholders into the kernel call tree diff --git a/src/pystencilssfg/ir/source_components.py b/src/pystencilssfg/ir/source_components.py index ea43ac8e06cd7520c75eb266c8ff9008ca7132a0..07ee848efccf3afd64a62a26db5c5d00eafa4218 100644 --- a/src/pystencilssfg/ir/source_components.py +++ b/src/pystencilssfg/ir/source_components.py @@ -2,149 +2,74 @@ from __future__ import annotations from abc import ABC from enum import Enum, auto -from typing import TYPE_CHECKING, Sequence, Generator, TypeVar +from typing import ( + TYPE_CHECKING, + Sequence, + Generator, + Iterable, + TypeVar, + Generic, +) from dataclasses import replace from itertools import chain from pystencils import CreateKernelConfig, create_kernel, Field -from pystencils.codegen import Kernel, Parameter +from pystencils.codegen import Kernel from pystencils.types import PsType, PsCustomType -from ..lang import SfgVar, HeaderFile, void +from ..lang import SfgVar, SfgKernelParamVar, HeaderFile, void from ..exceptions import SfgException if TYPE_CHECKING: from . import SfgCallTreeNode - from ..context import SfgContext -class SfgEmptyLines: - def __init__(self, lines: int): - self._lines = lines +# ========================================================================================================= +# +# SEMANTICAL ENTITIES +# +# These classes model *code entities*, which represent *semantic components* of the generated files. +# +# ========================================================================================================= - @property - def lines(self) -> int: - return self._lines - - -class SfgHeaderInclude: - """Represent ``#include``-directives.""" - - def __init__( - self, header_file: HeaderFile, private: bool = False - ): - self._header_file = header_file - self._private = private - - @property - def file(self) -> str: - return self._header_file.filepath - - @property - def system_header(self): - return self._header_file.system_header - - @property - def private(self): - return self._private - - def __hash__(self) -> int: - return hash((self._header_file, self._private)) - - def __eq__(self, other: object) -> bool: - return ( - isinstance(other, SfgHeaderInclude) - and self._header_file == other._header_file - and self._private == other._private - ) +class SfgCodeEntity: + """Base class for code entities. -class SfgKernelNamespace: - """A namespace grouping together a number of kernels.""" + Each code entity has a name and an optional enclosing namespace. + """ - def __init__(self, ctx: SfgContext, name: str): - self._ctx = ctx + def __init__(self, name: str, namespace: SfgNamespace | None) -> None: self._name = name - self._kernel_functions: dict[str, Kernel] = dict() + self._namespace: SfgNamespace | None = namespace @property - def name(self): + def name(self) -> str: + """Name of this entity""" return self._name @property - def kernel_functions(self): - yield from self._kernel_functions.values() - - def get_kernel_function(self, khandle: SfgKernelHandle) -> Kernel: - if khandle.kernel_namespace is not self: - raise ValueError( - f"Kernel handle does not belong to this namespace: {khandle}" - ) - - return self._kernel_functions[khandle.kernel_name] - - def add(self, kernel: Kernel, name: str | None = None): - """Adds an existing pystencils AST to this namespace. - If a name is specified, the AST's function name is changed.""" - if name is not None: - astname = name + def fqname(self) -> str: + """Fully qualified name of this entity""" + if self._namespace is not None: + return self._namespace.fqname + "::" + self._name else: - astname = kernel.name + return self._name - if astname in self._kernel_functions: - raise ValueError( - f"Duplicate ASTs: An AST with name {astname} already exists in namespace {self._name}" - ) - - if name is not None: - kernel.name = name - - self._kernel_functions[astname] = kernel - - for header in kernel.required_headers: - self._ctx.add_include(SfgHeaderInclude(HeaderFile.parse(header), private=True)) - - return SfgKernelHandle(self._ctx, astname, self, kernel.parameters) - - def create( - self, - assignments, - name: str | None = None, - config: CreateKernelConfig | None = None, - ): - """Creates a new pystencils kernel from a list of assignments and a configuration. - This is a wrapper around `pystencils.create_kernel` - with a subsequent call to `add`. - """ - if config is None: - config = CreateKernelConfig() + @property + def namespace(self) -> SfgNamespace | None: + """Parent namespace of this entity""" + return self._namespace - if name is not None: - if name in self._kernel_functions: - raise ValueError( - f"Duplicate ASTs: An AST with name {name} already exists in namespace {self._name}" - ) - config = replace(config, function_name=name) - # type: ignore - ast = create_kernel(assignments, config=config) - return self.add(ast) +class SfgKernelHandle(SfgCodeEntity): + """Handle to a pystencils kernel.""" + def __init__(self, name: str, namespace: SfgKernelNamespace, kernel: Kernel): + super().__init__(name, namespace) -class SfgKernelHandle: - """A handle that represents a pystencils kernel within a kernel namespace.""" - - def __init__( - self, - ctx: SfgContext, - name: str, - namespace: SfgKernelNamespace, - parameters: Sequence[Parameter], - ): - self._ctx = ctx - self._name = name - self._namespace = namespace - self._parameters = [SfgKernelParamVar(p) for p in parameters] + self._kernel = kernel + self._parameters = [SfgKernelParamVar(p) for p in kernel.parameters] self._scalar_params: set[SfgVar] = set() self._fields: set[Field] = set() @@ -155,82 +80,49 @@ class SfgKernelHandle: else: self._scalar_params.add(param) - @property - def kernel_name(self): - return self._name - - @property - def kernel_namespace(self): - return self._namespace - - @property - def fully_qualified_name(self): - match self._ctx.fully_qualified_namespace: - case None: - return f"{self.kernel_namespace.name}::{self.kernel_name}" - case fqn: - return f"{fqn}::{self.kernel_namespace.name}::{self.kernel_name}" - @property def parameters(self) -> Sequence[SfgKernelParamVar]: + """Parameters to this kernel""" return self._parameters @property def scalar_parameters(self) -> set[SfgVar]: + """Scalar parameters to this kernel""" return self._scalar_params @property def fields(self): + """Fields accessed by this kernel""" return self._fields - def get_kernel_function(self) -> Kernel: - return self._namespace.get_kernel_function(self) - - -SymbolLike_T = TypeVar("SymbolLike_T", bound=Parameter) - + def get_kernel(self) -> Kernel: + """Underlying pystencils kernel object""" + return self._kernel -class SfgKernelParamVar(SfgVar): - __match_args__ = ("wrapped",) - """Cast pystencils- or SymPy-native symbol-like objects as a `SfgVar`.""" - - def __init__(self, param: Parameter): - self._param = param - super().__init__(param.name, param.dtype) - - @property - def wrapped(self) -> Parameter: - return self._param - - def _args(self): - return (self._param,) - - -class SfgFunction: +class SfgFunction(SfgCodeEntity): __match_args__ = ("name", "tree", "parameters") def __init__( self, name: str, + namespace: SfgNamespace | None, tree: SfgCallTreeNode, return_type: PsType = void, - _is_method: bool = False, + inline: bool = False, ): - self._name = name + super().__init__(name, namespace) + self._tree = tree self._return_type = return_type + self._inline = inline self._parameters: set[SfgVar] - if not _is_method: - from .postprocessing import CallTreePostProcessing - param_collector = CallTreePostProcessing() - self._parameters = param_collector(self._tree).function_params + from .postprocessing import CallTreePostProcessing - @property - def name(self) -> str: - return self._name + param_collector = CallTreePostProcessing() + self._parameters = param_collector(self._tree).function_params @property def parameters(self) -> set[SfgVar]: @@ -244,6 +136,10 @@ class SfgFunction: def return_type(self) -> PsType: return self._return_type + @property + def inline(self) -> bool: + return self._inline + class SfgVisibility(Enum): DEFAULT = auto() @@ -308,50 +204,6 @@ class SfgClassMember(ABC): self._vis = vis -class SfgVisibilityBlock: - def __init__(self, visibility: SfgVisibility) -> None: - self._vis = visibility - self._members: list[SfgClassMember] = [] - self._cls: SfgClass | None = None - - @property - def visibility(self) -> SfgVisibility: - return self._vis - - def append_member(self, member: SfgClassMember): - if self._cls is not None: - self._cls._add_member(member, self._vis) - self._members.append(member) - - def members(self) -> Generator[SfgClassMember, None, None]: - yield from self._members - - @property - def is_bound(self) -> bool: - return self._cls is not None - - def _bind(self, cls: SfgClass): - if self._cls is not None: - raise SfgException( - f"Binding visibility block to class {cls.class_name} failed: " - f"was already bound to {self._cls.class_name}" - ) - self._cls = cls - - -class SfgInClassDefinition(SfgClassMember): - def __init__(self, text: str): - SfgClassMember.__init__(self) - self._text = text - - @property - def text(self) -> str: - return self._text - - def __str__(self) -> str: - return self._text - - class SfgMemberVariable(SfgVar, SfgClassMember): def __init__(self, name: str, dtype: PsType): SfgVar.__init__(self, name, dtype) @@ -367,29 +219,16 @@ class SfgMethod(SfgFunction, SfgClassMember): inline: bool = False, const: bool = False, ): - SfgFunction.__init__(self, name, tree, return_type=return_type, _is_method=True) + SfgFunction.__init__(self, name, tree, return_type=return_type, inline=inline) SfgClassMember.__init__(self) - self._inline = inline self._const = const self._parameters: set[SfgVar] = set() - @property - def inline(self) -> bool: - return self._inline - @property def const(self) -> bool: return self._const - def _bind(self, cls: SfgClass, vis: SfgVisibility): - super()._bind(cls, vis) - - from .postprocessing import CallTreePostProcessing - - param_collector = CallTreePostProcessing(enclosing_class=cls) - self._parameters = param_collector(self._tree).function_params - class SfgConstructor(SfgClassMember): __match_args__ = ("parameters", "initializers", "body") @@ -418,7 +257,7 @@ class SfgConstructor(SfgClassMember): return self._body -class SfgClass: +class SfgClass(SfgCodeEntity): """Models a C++ class. ### Adding members to classes @@ -430,23 +269,22 @@ class SfgClass: accessible through the `default` property. To add members with custom visibility, create a new SfgVisibilityBlock, add members to the block, and add the block using `append_visibility_block`. - - A more succinct interface for constructing classes is available through the - [SfgClassComposer][pystencilssfg.composer.SfgClassComposer]. """ __match_args__ = ("class_name",) def __init__( self, - class_name: str, + name: str, + namespace: SfgNamespace | None, class_keyword: SfgClassKeyword = SfgClassKeyword.CLASS, bases: Sequence[str] = (), ): if isinstance(bases, str): raise ValueError("Base classes must be given as a sequence.") - self._class_name = class_name + super().__init__(name, namespace) + self._class_keyword = class_keyword self._bases_classes = tuple(bases) @@ -454,18 +292,14 @@ class SfgClass: self._default_block._bind(self) self._blocks = [self._default_block] - self._definitions: list[SfgInClassDefinition] = [] self._constructors: list[SfgConstructor] = [] self._methods: list[SfgMethod] = [] self._member_vars: dict[str, SfgMemberVariable] = dict() - @property - def class_name(self) -> str: - return self._class_name - @property def src_type(self) -> PsType: - return PsCustomType(self._class_name) + # TODO: Use CppTypeFactory instead + return PsCustomType(self._name) @property def base_classes(self) -> tuple[str, ...]: @@ -504,14 +338,6 @@ class SfgClass: for b in filter(lambda b: b.visibility == visibility, self._blocks) ) - def definitions( - self, visibility: SfgVisibility | None = None - ) -> Generator[SfgInClassDefinition, None, None]: - if visibility is not None: - yield from filter(lambda m: m.visibility == visibility, self._definitions) - else: - yield from self._definitions - def member_variables( self, visibility: SfgVisibility | None = None ) -> Generator[SfgMemberVariable, None, None]: @@ -547,16 +373,11 @@ class SfgClass: self._add_member_variable(member) elif isinstance(member, SfgMethod): self._add_method(member) - elif isinstance(member, SfgInClassDefinition): - self._add_definition(member) else: raise SfgException(f"{member} is not a valid class member.") member._bind(self, vis) - def _add_definition(self, definition: SfgInClassDefinition): - self._definitions.append(definition) - def _add_constructor(self, constr: SfgConstructor): self._constructors.append(constr) @@ -566,7 +387,288 @@ class SfgClass: def _add_member_variable(self, variable: SfgMemberVariable): if variable.name in self._member_vars: raise SfgException( - f"Duplicate field name {variable.name} in class {self._class_name}" + f"Duplicate field name {variable.name} in class {self._name}" ) self._member_vars[variable.name] = variable + + +SourceEntity_T = TypeVar( + "SourceEntity_T", bound=SfgFunction | SfgClassMember | SfgClass, covariant=True +) +"""Source entities that may have declarations and definitions.""" + + +# ========================================================================================================= +# +# SYNTACTICAL ELEMENTS +# +# These classes model *code elements*, which represent the actual syntax objects that populate the output +# files, their namespaces and class bodies. +# +# ========================================================================================================= + + +class SfgEntityDecl(Generic[SourceEntity_T]): + """Declaration of a function, class, method, or constructor""" + + __match_args__ = ("entity",) + + def __init__(self, entity: SourceEntity_T) -> None: + self._entity = entity + + @property + def entity(self) -> SourceEntity_T: + return self._entity + + +class SfgEntityDef(Generic[SourceEntity_T]): + """Definition of a function, class, method, or constructor""" + + __match_args__ = ("entity",) + + def __init__(self, entity: SourceEntity_T) -> None: + self._entity = entity + + @property + def entity(self) -> SourceEntity_T: + return self._entity + + +SfgClassBodyElement = ( + str + | SfgEntityDecl[SfgClassMember] + | SfgEntityDef[SfgClassMember] + | SfgMemberVariable +) +"""Elements that may be placed in the visibility blocks of a class body.""" + + +class SfgVisibilityBlock: + """Visibility-qualified block inside a class definition body. + + Visibility blocks host the code elements placed inside a class body: + method and constructor declarations, + in-class method and constructor definitions, + as well as variable declarations and definitions. + + Args: + visibility: The visibility qualifier of this block + """ + + def __init__(self, visibility: SfgVisibility) -> None: + self._vis = visibility + self._elements: list[SfgClassBodyElement] = [] + self._cls: SfgClass | None = None + + @property + def visibility(self) -> SfgVisibility: + return self._vis + + def append_member(self, element: SfgClassBodyElement): + if isinstance(element, (SfgEntityDecl, SfgEntityDef)): + member = element.entity + elif isinstance(element, SfgClassMember): + member = element + else: + member = None + + if self._cls is not None and member is not None: + self._cls._add_member(member, self._vis) + + self._elements.append(element) + + @property + def elements(self) -> tuple[SfgClassBodyElement, ...]: + return tuple(self._elements) + + def members(self) -> Generator[SfgClassMember, None, None]: + for elem in self._elements: + match elem: + case SfgEntityDecl(entity) | SfgEntityDef(entity): + yield entity + case SfgMemberVariable(): + yield elem + + @property + def is_bound(self) -> bool: + return self._cls is not None + + def _bind(self, cls: SfgClass): + if self._cls is not None: + raise SfgException( + f"Binding visibility block to class {cls.class_name} failed: " + f"was already bound to {self._cls.class_name}" + ) + self._cls = cls + + +class SfgNamespace: + """A C++ namespace. + + Each namespace has a `name` and a `parent`; its fully qualified name is given as + ``<parent.name>::<name>``. + + Args: + name: Local name of this namespace + parent: Parent namespace enclosing this namespace + """ + + def __init__(self, name: str, parent: SfgNamespace | None) -> None: + self._name: str = name + self._parent: SfgNamespace | None = parent + self._elements: list[SfgNamespaceElement] = [] + + @property + def name(self) -> str: + """The name of this namespace""" + return self._name + + @property + def fqname(self) -> str: + """The fully qualified name of this namespace""" + if self._parent is not None: + return self._parent.fqname + "::" + self._name + else: + return self._name + + @property + def elements(self) -> list[SfgNamespaceElement]: + """Sequence of source elements that make up the body of this namespace""" + return self._elements + + @elements.setter + def elements(self, elems: Iterable[SfgNamespaceElement]): + self._elements = list(elems) + + +class SfgKernelNamespace(SfgNamespace): + """A namespace grouping together a number of kernels.""" + + def __init__(self, name: str, parent: SfgNamespace | None): + super().__init__(name, parent) + self._kernels: dict[str, SfgKernelHandle] = [] + + @property + def name(self): + return self._name + + @property + def kernels(self) -> tuple[SfgKernelHandle, ...]: + return tuple(self._kernels.values()) + + def add(self, kernel: Kernel, name: str | None = None): + """Adds an existing pystencils AST to this namespace. + If a name is specified, the AST's function name is changed.""" + if name is None: + kernel_name = kernel.name + else: + kernel_name = name + + if kernel_name in self._kernels: + raise ValueError( + f"Duplicate kernels: A kernel called {kernel_name} already exists in namespace {self.fqname}" + ) + + if name is not None: + kernel.name = kernel_name + + khandle = SfgKernelHandle(kernel_name, self, kernel) + self._kernels[kernel_name] = khandle + + # TODO: collect includes later + # for header in kernel.required_headers: + # self._ctx.add_include( + # SfgHeaderInclude(HeaderFile.parse(header), private=True) + # ) + + return khandle + + def create( + self, + assignments, + name: str | None = None, + config: CreateKernelConfig | None = None, + ): + """Creates a new pystencils kernel from a list of assignments and a configuration. + This is a wrapper around `pystencils.create_kernel` + with a subsequent call to `add`. + """ + if config is None: + config = CreateKernelConfig() + + if name is not None: + if name in self._kernels: + raise ValueError( + f"Duplicate kernels: A kernel with name {name} already exists in namespace {self.fqname}" + ) + config = replace(config, function_name=name) + + # type: ignore + kernel = create_kernel(assignments, config=config) + return self.add(kernel) + + +SfgNamespaceElement = str | SfgNamespace | SfgEntityDecl | SfgEntityDef +"""Elements that may be placed inside a namespace, including the global namespace.""" + + +class SfgSourceFileType(Enum): + HEADER = auto() + TRANSLATION_UNIT = auto() + + +class SfgSourceFile: + """A C++ source file. + + Args: + name: Name of the file (without parent directories), e.g. ``Algorithms.cpp`` + file_type: Type of the source file (header or translation unit) + prelude: Optionally, text of the prelude comment printed at the top of the file + """ + + def __init__( + self, name: str, file_type: SfgSourceFileType, prelude: str | None = None + ) -> None: + self._name: str = name + self._file_type: SfgSourceFileType = file_type + self._prelude: str | None = prelude + self._includes: list[HeaderFile] = [] + self._elements: list[SfgNamespaceElement] = [] + + @property + def name(self) -> str: + """Name of this source file""" + return self._name + + @property + def file_type(self) -> SfgSourceFileType: + """File type of this source file""" + return self._file_type + + @property + def prelude(self) -> str | None: + """Text of the prelude comment""" + return self._prelude + + @prelude.setter + def prelude(self, text: str | None): + self._prelude = text + + @property + def includes(self) -> list[HeaderFile]: + """Sequence of header files to be included at the top of this file""" + return self._includes + + @includes.setter + def includes(self, incl: Iterable[HeaderFile]): + self._includes = list(incl) + + @property + def elements(self) -> list[SfgNamespaceElement]: + """Sequence of source elements comprising the body of this file""" + return self._elements + + @elements.setter + def elements(self, elems: Iterable[SfgNamespaceElement]): + self._elements = list(elems) diff --git a/src/pystencilssfg/lang/__init__.py b/src/pystencilssfg/lang/__init__.py index 9218ec2b7d7f94517e35a2c9a8e4e4ddaa7c3a2a..a8de86be10ce44c2ac2d49cc3b5fba0e1549de50 100644 --- a/src/pystencilssfg/lang/__init__.py +++ b/src/pystencilssfg/lang/__init__.py @@ -2,6 +2,7 @@ from .headers import HeaderFile from .expressions import ( SfgVar, + SfgKernelParamVar, AugExpr, VarLike, _VarLike, @@ -21,6 +22,7 @@ from .types import cpptype, void, Ref, strip_ptr_ref __all__ = [ "HeaderFile", "SfgVar", + "SfgKernelParamVar", "AugExpr", "VarLike", "_VarLike", diff --git a/src/pystencilssfg/lang/expressions.py b/src/pystencilssfg/lang/expressions.py index f86140ee7a3775caab19f69c34ef97822975b95e..4a1f7e9e7aa49d0534f4d27e3b038eb7c72d3c25 100644 --- a/src/pystencilssfg/lang/expressions.py +++ b/src/pystencilssfg/lang/expressions.py @@ -6,6 +6,7 @@ from abc import ABC, abstractmethod import sympy as sp from pystencils import TypedSymbol +from pystencils.codegen import Parameter from pystencils.types import PsType, UserTypeSpec, create_type from ..exceptions import SfgException @@ -74,6 +75,23 @@ class SfgVar: return self.name_and_type() +class SfgKernelParamVar(SfgVar): + __match_args__ = ("wrapped",) + + """Cast pystencils- or SymPy-native symbol-like objects as a `SfgVar`.""" + + def __init__(self, param: Parameter): + self._param = param + super().__init__(param.name, param.dtype) + + @property + def wrapped(self) -> Parameter: + return self._param + + def _args(self): + return (self._param,) + + class DependentExpression: """Wrapper around a C++ expression code string, annotated with a set of variables and a set of header files this expression depends on.