sofia.ir package

Submodules

Module contents

class sofia.ir.Assignment(lhs, rhs)

Bases: Assignment

default_assumptions = {}
class sofia.ir.Checkpoint(*args, **kwargs)

Bases: CodegenToken

Insert a checkpoint (for autodiff) around the code generated from body.

The intermediate values computed inside the body are not stored for the backward pass in autodiff, and are instead recomputed. See e.g. https://docs.jax.dev/en/latest/_autosummary/jax.checkpoint.html for more on checkpointing.

Outside autodiff this token has no effect.

Parameters:

body (sofia.ir.base.CodeBlock) – Block of IR to checkpoint.

Examples

Checkpoint the body of a batched For.

```python from sofia import Field, Grid, ir

G = Grid(ndim=1, _shape=(64,)) u = Field(“u”, domain=G)

code = ir.For(

100, ir.Checkpoint(

ir.For( 100, ir.EvalAssignment(u, u * 2 + 1) ),

),

)

To checkpoint an entire loop body, it’s better to use ir.For(..., checkpoint=True) which will apply checkpointing in the same way as the example above.

See also

For

For loop token, whose checkpoint argument checkpoints the body of the loop.

body: CodeBlock
default_assumptions = {}
defaults = {}
class sofia.ir.CodeBlock(*args)

Bases: CodeBlock

Basic building block for blocks of code.

Any chunks of IR can be wrapped inside a CodeBlock. This can be necessary for example when a block of code is passed as an argument to another IR statement such as BatchedScan.

Examples

>>> import numpy as np
>>> from sofia import Field, Grid, ir, indices
>>> from sofia.pde import simulate
>>> G = Grid(ndim=1)
>>> u = Field("u", domain=G)
>>> v = Field("v", domain=G)
>>> i = indices.i
>>> code_block = ir.CodeBlock(
... ir.EvalAssignment(u, u[i-1] + v[i-1]),  # u_i <- u_{i-1} + v_{i-1}
... ir.EvalAssignment(v, u[i+1] + v[i+1]),  # v_i <- u_{i+1} + v_{i+1}
... )

Repeat the above code block 5 times, assigning output to us:

>>> scan = ir.CodeBlock(
...    ir.Inputs((u, v,)),
...    ir.For(
...        length=5,
...        body=code_block,
...    ),
...    ir.Outputs(u,)
... )
>>> N = 10
>>> u = v = np.arange(N)
>>> #res = simulate(scan, {G: dict(shape=N)}, u, v) # TODO: fix
>>> #res
# (array([[270, 259, 298, 377, 466, 535, 544, 503, 422, 331]]), array([[635, 509, 443, 447, 531, 665, 789, 853, 847, 761]]))
default_assumptions = {}
class sofia.ir.CodegenToken(*args, **kwargs)

Bases: _Token

Base class for IR tokens that can be handled by codegen.

These tokens cannot be transformed into lower-level tokens (either through transform or doit). Therefore, these tokens must have an associated _print_{TokenName} method on the codegen printer.

Note that this class only has semantic meaning, it does not validate whether the codegen printer actually supports any subclass.

Examples

```python from sofia import ir import sympy as sp

class MyToken(ir.CodegenToken):

lhs: object rhs: object = “default”

my_token = MyToken(lhs=sp.x, rhs=”something”) ```

See also

CustomToken

For examples and the full explanation for defining fields.

default_assumptions = {}
defaults = {}
class sofia.ir.CustomFunctionCall(*args, **kwargs)

Bases: Token

name
lhs
input_args
default_assumptions = {}
class sofia.ir.CustomToken(*args, **kwargs)

Bases: _Token

Base class for defining custom internal representation (IR) tokens.

This class provides a convenient way to define your own custom tokens with minimal boilerplate. The token is defined by a set of fields (with optional defaults) and a transform method to represent it in terms of other IR tokens.

Fields are declared as class variables with attrs annotations. Fields without a default are defined as my_field: object, while providing a default is as simple as my_field: object = “default_value”.

A field can also declare a converter via attrs.field(converter=…), in which case the field’s own (stored) type is given by the annotation, while the type accepted by __init__ (as seen by type checkers) is inferred from the converter’s argument type. Converters must be idempotent (i.e. conv(conv(x)) == conv(x)), as sympy requires that Token(*token.args) == token.

Subclasses must implement transform returning the representation of the token in terms of other IR tokens. This method is called from doit, which is used by sympy to expand an IR tree.

Examples

```python import sympy as sp from sofia import ir

class Assignment(ir.CustomToken):

lhs: sp.Symbol = field(converter=sp.sympify) rhs: sp.Expr = field(converter=sp.sympify)

def transform(self):

return ir.Assignment(self.lhs, self.rhs)

```

abstractmethod transform() → Basic

Represent this token in terms of other IR tokens.

All returned tokens must (eventually) expand to just tokens defined in ir.base (e.g. ir.base.For), as the code generator is only defined for those tokens. Note that tt is okay for this method to return other custom tokens not from ir.base, as long as these tokens (possibly through multiple .transform() calls) eventually expand to just ir.base tokens.

Subclasses must override this method.

Returns:

The token in terms of other IR tokens.

Return type:

sympy.Basic

default_assumptions = {}
defaults = {}
doit(**hints) → Basic

Expand this token into its transform representation.

This makes CustomToken compatible with sympy’s expansion which calls doit recursively on an IR tree.

Returns:

The token in terms of other tokens.

Return type:

sympy.Basic

class sofia.ir.EvalAssignment(lhs, rhs)

Bases: CodegenAST

Evaluate the rhs expression on the full grid.

The result will be stored in the lhs symbol, which should be a Field or sp.Indexed object.

Examples

Evaluate a constant expression and store in the 2D field u:

>>> from sofia import Field, Grid
>>> G = Grid(ndim=2);
>>> u = Field('u', domain=G)
>>> v = Field('v', domain=G)
>>> i, j = G.indices
>>> EvalAssignment(u, 3)
EvalAssignment(u, 3)

If v is a Field the indices are optional:

>>> EvalAssignment(v, 3)
EvalAssignment(v, 3)

Evaluate a mathematical function:

>>> x = G.x
>>> EvalAssignment(u, sp.sin(3 * x) + x**2)
EvalAssignment(u, global.x**2 + sin(3*global.x))

Evaluate an expression containing other fields:

>>> EvalAssignment(u, u[i + 1, j] - 3 * v[i, j])
EvalAssignment(u, u[i + 1, j] - 3*v[i, j])

Also on the rhs default indices are optional for Field objects:

>>> EvalAssignment(u, u[i + 1, j] - 3 * v)
EvalAssignment(u, -3*v + u[i + 1, j])
Parameters:
  • lhs – Symbol representing the left-hand side, in which the result is to be stored.

  • rhs – Right-hand side expression that will be evaluated.

default_assumptions = {}
class sofia.ir.EddyViscositySmagorinsky(*args, **kwargs)

Bases: Token

Compute the Eddy viscosity for a given subgrid scale model.

Note: only the Smagorinsky model is supported right now.

Parameters:
  • velocities – Tuple of (u,v,w) fields.

  • nu_sgs – The GridField to be used for storing the output.

  • Cs – Smagorinsky constant.

  • strain_squaring –

    Mode of averaging: “edge” (default) square each off-diagonal strain at its own edge, then

    average to the centre. Preserves the adjointness that makes the reported SGS dissipation equal the applied dissipation. I.e. average-of-square.

    ”center” transfer the strains to the centre and square there. Reproduces

    the square-of-average convention of SBP-derived formulations.

defaults = {'Cs': 0.1, 'strain_squaring': 'edge'}
doit(deep=False, **hints)

Evaluate objects that are not evaluated by default like limits, integrals, sums and products. All objects of this kind will be evaluated recursively, unless some species were excluded via ‘hints’ or unless the ‘deep’ hint was set to ‘False’.

>>> from sympy import Integral
>>> from sympy.abc import x
>>> 2*Integral(x, x)
2*Integral(x, x)
>>> (2*Integral(x, x)).doit()
x**2
>>> (2*Integral(x, x)).doit(deep=False)
2*Integral(x, x)
velocities
nu_sgs
Cs
strain_squaring
default_assumptions = {}
class sofia.ir.For(*args, **kwargs)

Bases: CustomToken

A for-loop: repeat a block of code (body) a fixed number of times.

The input fields of body are carried and output along with its output fields. A field that is only ever written inside the body is considered local and not carried nor returned.

Parameters:
  • length (sympy.core.numbers.Integer | sympy.core.symbol.Symbol) – Number of iterations.

  • body (sofia.ir.base.CodeBlock) – Loop body.

  • output_fields (sympy.core.containers.Tuple) –

    Fields to report as outputs in addition to the loop carry.

    Warning

    Not currently supported.

  • checkpoint (bool) – If True (default), checkpoint the loop body under automatic differentiation. See Checkpoint for details.

Examples

```python from sofia import Field, Grid, ir

G = Grid(ndim=1, _shape=(64,)) u = Field(“u”, domain=G)

code = ir.For(1000, [ir.EvalAssignment(u, u * 2 + 1)]) ```

The same loop with a checkpointed body

`python code = ir.For(1000, [ir.EvalAssignment(u, u * 2 + 1)], checkpoint=True) `

See also

Checkpoint

Checkpoint an arbitrary block, rather than a whole loop body.

length: Integer | Symbol
body: CodeBlock
output_fields: Tuple
checkpoint: bool
transform()

Represent this token in terms of other IR tokens.

All returned tokens must (eventually) expand to just tokens defined in ir.base (e.g. ir.base.For), as the code generator is only defined for those tokens. Note that tt is okay for this method to return other custom tokens not from ir.base, as long as these tokens (possibly through multiple .transform() calls) eventually expand to just ir.base tokens.

Subclasses must override this method.

Returns:

The token in terms of other IR tokens.

Return type:

sympy.Basic

default_assumptions = {}
defaults = {'checkpoint': False, 'output_fields': ()}
class sofia.ir.FunctionCall(*args, **kwargs)

Bases: FunctionCall

name
function_args
default_assumptions = {}
class sofia.ir.FunctionDefinition(*args, **kwargs)

Bases: FunctionDefinition

body
default_assumptions = {}
class sofia.ir.IndicesDeclaration(*args, **kwargs)

Bases: Token

symbols
default_assumptions = {}
class sofia.ir.Inputs(*args, **kwargs)

Bases: Token

Specify inputs of the program.

Fields or other symbols specified by Inputs in the IR will be linked to input arguments (in order) of the generated code object.

Note

This is a special statement that can only appear once in the IR. It is optional - if omitted, the generated code object will not accept any arguments.

Parameters:

symbols – Symbol or tuple of symbols for which input arguments are to be expected.

symbols
default_assumptions = {}
class sofia.ir.KernelExtension(*args)

Bases: Function

Base class for a pointwise function inside a kernel expression.

A subclass is used like any sympy function, e.g. chi << InsideBody(geo, x, y, z), and appears in kernel expressions. Its code is printed by the method named after the kernel target, which receives the arguments already printed and returns the printed call:

  • warp(self, *args: str) -> str for Warp kernels

  • backend(self, *args: str) -> str for backend (numpy / jax) kernels

A subclass implements one or both; codegen picks a kernel target the subclass implements. Arguments are sympified on construction, as for any sympy function, so codegen can traverse and cache expressions containing them.

runtime_imports

Import lines the printed code needs, added to the generated module.

Type:

ClassVar[tuple[str, …]]

Examples

```python from sofia import ir

class InsideBody(ir.KernelExtension):
runtime_imports = (

“from sofia_contrib_kernels.ibm.kernels import inside_body”,

)

def warp(self, geo, x, y, z):

return f”inside_body({geo}, {x}, {y}, {z})”

```

runtime_imports: ClassVar[tuple[str, ...]] = ()
default_assumptions = {}
class sofia.ir.Load(*args, **kwargs)

Bases: CodegenToken

Load fields from a .npz file.

Parameters:
  • filename (str) – Path to the file to read.

  • fields (sympy.core.containers.Tuple) – Fields to populate. Names are matched against the archive keys.

  • clip_larger (bool) – If True (default), arrays larger than the target field are clipped to fit.

filename: str
fields: Tuple
clip_larger: bool
default_assumptions = {}
defaults = {'clip_larger': True}
class sofia.ir.Outputs(*args, **kwargs)

Bases: Token

Specify outputs of the program.

Any fields or other symbols can be added to the output by using this statement in the IR. Note that the position where the Output appears in the IR is significant - any changes to the fields afterwards are not take into account.

Parameters:

symbols – Symbol or tuple of symbols that should be added to the output of the program.

symbols
default_assumptions = {}
class sofia.ir.Pass(*args, **kwargs)

Bases: Token

default_assumptions = {}
class sofia.ir.Print(*args, **kwargs)

Bases: Print

print_args
format_string
file
default_assumptions = {}
class sofia.ir.PrintTime(*args, **kwargs)

Bases: CodegenToken

Print elapsed wall-clock time at this point in the program.

show: bool
default_assumptions = {}
defaults = {'show': True}
class sofia.ir.ProgramContext(*args, **kwargs)

Bases: Protocol

What ProgramExtension.emit can add to the generated module.

Each method that adds code returns the code that uses it, to be put in the statement emit returns.

print_in_kernel(expr: Basic) → str

Print expr as it appears in a Warp kernel, e.g. a field’s array.

Use it for code in the body given to warp_kernel.

print_in_program(expr: Basic) → str

Print expr as it appears in the program, outside any kernel.

E.g. a field’s variable, or the unique name of an sp.Dummy. Use it for the statement emit returns and the body given to function.

add_warp_func(expr: Basic, args: Iterable[Symbol] | None = None) → str

Compile expr into a @warp.func and return the function’s name.

expr is either a scalar expression, compiled to a function of float64 arguments, or an sp.Tuple of 2 or 3 components, each in one symbol, compiled to a function of one vec2d or vec3d.

args sets a scalar function’s arguments, in order (default: the symbols in expr, in no fixed order), so e.g. a constant still takes them and returns float64.

warp_kernel(output: Basic, inputs: Iterable[Basic], body: Callable[[str], str], assign: bool = True) → str

Add a @warp.kernel that computes output and return its call.

body(out) returns the kernel body, given the name of the array the kernel writes. That array is separate from output’s input array, so the body reads the old values of output. The returned statement assigns the result to output; with assign=False, it is only the call, e.g. to assign to a temporary named with print_in_program.

function(body: str, inputs: Iterable[Basic], outputs: Iterable[Basic]) → str

Wrap the statements body in a backend function and return its call.

The function takes inputs and returns outputs, which the returned statement assigns.

class sofia.ir.ProgramExtension(*args, **kwargs)

Bases: CodegenToken

Base class for a statement in the generated program.

A subclass is an IR token, with fields declared as for CodegenToken, and is placed in an ir.CodeBlock like any other statement. Codegen calls emit with a ProgramContext, which adds kernels and functions to the generated module, and puts the returned statement in main.

All fields must hold sympy objects, so that the token can be traversed, substituted and cached by hash. Use a converter for other values, e.g. maxiter: sp.Integer = field(converter=sp.sympify, default=8).

runtime_imports

Import lines the emitted code needs, added to the generated module.

Type:

ClassVar[tuple[str, …]]

Examples

```python from sofia import Field, Geometry, ir

class GhostCellFill(ir.ProgramExtension):
runtime_imports = (

“from sofia_contrib_kernels.ibm.kernels import ghost_value”,

)

field: Field geometry: Geometry

@property def inputs(self):

return {self.field, self.geometry}

@property def outputs(self):

return {self.field}

def emit(self, ctx):

geo, f = ctx.print_in_kernel(self.geometry), ctx.print_in_kernel(self.field) return ctx.warp_kernel(

output=self.field, inputs=(self.geometry, self.field), body=lambda out: (

“i, j, k = warp.tid()n” f”{out}[i, j, k] = ghost_value({geo}, {f}, i, j, k)”

),

)

```

runtime_imports: ClassVar[tuple[str, ...]] = ()
abstract property inputs: set

Fields, scalars and geometries the statement reads.

abstract property outputs: set

Fields and scalars the statement writes.

abstractmethod emit(ctx: ProgramContext) → str

Return the statement for main, adding what it calls through ctx.

default_assumptions = {}
defaults = {}
class sofia.ir.ProjectDivFree(*args, **kwargs)

Bases: Token

Makes velocity fields divergence free by solving the Poisson equation.

Only uniform axes (without curvilinear coordinate transformations) are supported for now.

This token expands to
  • ir.SolveSeparable: solves the Poisson equations, using

    sofia.pde.laplacian(pressure, allow_expand=False) as the linear operator

  • Assignments that project out the divergence on the velocity fields

Due to the forwarding to ir.SolveSeparable, restrictions on the allowed boundary conditions apply. Per axis, the following boundary conditions are supported:

  • periodic (solved via Fourier transform)

  • cell-centered neumann-neumann boundary conditions (via DCT-I)

  • edge neumann-neumann boundary conditions (via DCT-II)

  • mixed dirichlet/neumann boundary conditions at arbitrary

    locations, but not neumann-neumann since the linear system becomes singular

One further restriction: at most one axis of the last type is allowed.

Parameters:
  • velocities – Fields that store velocities, to be made divergence-free.

  • pressure – Field that represents the pressure. The contents of the pressure will be computed as part of this token and stored in this field.

  • bcs – Boundary conditions on pressure that should be enforced. This should be an sp.Piecewise expression with boundary conditions at the boundaries and pressure itself in the bulk. The best way to obtain this is via sofia.pde.apply_boundary_conditions(bcs, pressure).

defaults = {'bcs': NoneToken()}
doit(deep=False, **hints)

Evaluate objects that are not evaluated by default like limits, integrals, sums and products. All objects of this kind will be evaluated recursively, unless some species were excluded via ‘hints’ or unless the ‘deep’ hint was set to ‘False’.

>>> from sympy import Integral
>>> from sympy.abc import x
>>> 2*Integral(x, x)
2*Integral(x, x)
>>> (2*Integral(x, x)).doit()
x**2
>>> (2*Integral(x, x)).doit(deep=False)
2*Integral(x, x)
velocities
pressure
bcs
default_assumptions = {}
class sofia.ir.RKn(*args, **kwargs)

Bases: CustomToken

F: sp.Dict[Field, sp.Expr]
order: sp.Symbol
dt: sp.Basic
post: ast.CodegenAST
transform() → Basic

Represent this token in terms of other IR tokens.

All returned tokens must (eventually) expand to just tokens defined in ir.base (e.g. ir.base.For), as the code generator is only defined for those tokens. Note that tt is okay for this method to return other custom tokens not from ir.base, as long as these tokens (possibly through multiple .transform() calls) eventually expand to just ir.base tokens.

Subclasses must override this method.

Returns:

The token in terms of other IR tokens.

Return type:

sympy.Basic

default_assumptions = {}
defaults = {'post': None}
class sofia.ir.Save(*args, **kwargs)

Bases: CodegenToken

Save fields to disk during simulation.

Parameters:
  • out_folder (str) – Path to location on disk where the output will be stored.

  • fields (sympy.core.containers.Tuple) – Fields that are to be saved.

  • sequential (bool) – If True (default), every time a Save statement is executed the filename will have an index appended to it, making sure outputs are stored in separate files. If False, any Save statement will overwrite the given file.

  • overwrite (bool) – If False (default), the out_folder should not exist prior to running the simulation (preventing accidental overwrites). If it does exist an exception is raised.

  • downcast_dtype (Literal['float16', 'float32'] | None) – Before saving, each field can be converted to a set dtype in order to reduce file size. The options are (“float16”, “float32”). If omitted, all fields are saved in their original dtype.

  • use_threading (bool) – If True (default), saving will be performed in a separate thread without blocking the execution of the simulation. Note that while this enhances performance, it is possible that the simulation finishes before all save threads are finished. This issue (potentially leading to data loss) will be solved in future updates but it’s safest for now to add a time.sleep at the end of the simulation.

out_folder: str
fields: Tuple
sequential: bool
overwrite: bool
downcast_dtype: Literal['float16', 'float32'] | None
use_threading: bool
default_assumptions = {}
defaults = {'downcast_dtype': None, 'overwrite': False, 'sequential': True, 'use_threading': True}
class sofia.ir.SolveBlockTridiagonal(*args, **kwargs)

Bases: CodegenToken

Solve a block-tridiagonal system of equations.

Parameters:
  • x (sympy.core.containers.Tuple) – Output field symbols.

  • exprs (sympy.core.containers.Tuple) – A tuple of symbolic matrices representing the blocks of the system. The matrices should be of the form (D, A, C) where D is the diagonal, A is the lower diagonal, and C is the upper diagonal.

  • b (sympy.core.containers.Tuple) – Discretised right hand side expressions.

x: Tuple
exprs: Tuple
b: Tuple
default_assumptions = {}
defaults = {}
class sofia.ir.SolveCirculant(*args, **kwargs)

Bases: Token

Solves a separable linear problem Ax=b with periodic boundary conditions.

Each axis should have periodic boundary conditions, since the system is solved using Fourier transforms.

Parameters:
  • x – Field to solve for (must be the same as in the exprs).

  • expr – Expression representing the linear operator acting on x.

  • b – Right-hand side Field.

x
expr
b
default_assumptions = {}
class sofia.ir.SolveSparse(*args, **kwargs)

Bases: CodegenToken

Solve A x = b, or A^T x = b, with a sparse direct solver.

Entries of A that couple to a neighbour outside of the grid are dropped, also along periodic axes.

Parameters:
  • x – The unknown fields to solve for. They must have the same locus. The solution is written to these fields.

  • exprs –

    The operator A, as either one expression per field of x or a single Jacobian.

    For the expressions, each may be:

    • a stencil, linear in the unknowns at shifted indices, e.g. x[i - 1].

    • an expression linear in x, whose derivatives are discretised.

    The Jacobian holds one residual per field of x. A is the derivative of these residuals with respect to the variables, rather than with respect to x. For a nonlinear residual, the entries of A depend on the variables, and are computed from the values the variables hold when the solve runs. The variables are only read, and are not overwritten by the solve. This is for example used in a Newton step: x is the update, and the variables hold the current value at which the residual is linearised. Also see the Newton example below.

  • b – The right-hand side, one expression per row.

  • transpose – Solve A^T x = b instead.

  • scale_rows – Scale each row of A and b by the largest entry of that row of A.

Raises:
  • TypeError – If x isn’t one or more GridField.

  • ValueError – If fields of x share a name, x, exprs and b have a different number of rows, or the fields don’t have the same locus.

Examples

A stencil, A x = b

```python from sofia import Grid, fields, ir

G = Grid(ndim=2) i, j = G.indices x, b = fields(“x b”, G)

A = 4 * x[i, j] - x[i - 1, j] - x[i, j - 1] - x[i + 1, j + 1] token = ir.SolveSparse(x, A, b) ```

An expression, coupling two 1D fields

```python from sofia.pde import diff

G = Grid(ndim=1) u, v, f, g = fields(“u v f g”, G)

token = ir.SolveSparse((u, v), (-diff(u, G.x, 2) + v, v - 2 * u), (f, g)) ```

A Jacobian, for a Newton step on the nonlinear residual R of a 2D field

```python from sofia.pde.jacobian import Jacobian

G = Grid(ndim=2) u, d, q = fields(“u d q”, G) R = -(diff(u, G.x, 2) + diff(u, G.y, 2)) + u**3 - q

token = ir.SolveSparse(d, Jacobian(R, u), -R) ```

x: sp.Tuple
exprs: sp.Tuple | Jacobian
b: sp.Tuple
transpose: Boolean
scale_rows: Boolean
default_assumptions = {}
defaults = {'scale_rows': True, 'transpose': False}
class sofia.ir.WallModelReconstruct(*args, **kwargs)

Bases: Token

Reconstruct near-wall velocity from a wall function.

Parameters:
  • wall_fn – Symbolic wall function in forward form u+ = f(y+), expressed in terms of y_plus. e.g. Reichardt’s law. The inverse form (y+ = g(u+), e.g. Spalding) is not accepted.

  • y_plus – The symbol used for y+ inside wall_fn. (will refactor into sp.Lambda later)

  • velocities – The velocity components (u, v, w), overwritten in place.

  • geometry – Geometry object.

  • nu – Kinematic viscosity.

  • band – Reconstruct only where the wall distance is below this.

  • match_height – Wall distance of the matching point used to infer u_tau. Must exceed band.

  • n_iter – Newton iterations for u_tau.

  • max_dist – Search radius passed to wp.mesh_query_point.

defaults = {'band': 2, 'match_height': 6, 'max_dist': 10.0, 'maxiter': 8, 'side': 1}
doit(deep=False, **hints)

Evaluate objects that are not evaluated by default like limits, integrals, sums and products. All objects of this kind will be evaluated recursively, unless some species were excluded via ‘hints’ or unless the ‘deep’ hint was set to ‘False’.

>>> from sympy import Integral
>>> from sympy.abc import x
>>> 2*Integral(x, x)
2*Integral(x, x)
>>> (2*Integral(x, x)).doit()
x**2
>>> (2*Integral(x, x)).doit(deep=False)
2*Integral(x, x)
wall_fn
y_plus
velocities
geometry
nu
band
match_height
maxiter
max_dist
side
default_assumptions = {}
class sofia.ir.WallModelUTau(*args, **kwargs)

Bases: Token

Computes near-wall friction velocity u_tau.

Parameters:
  • wall_fn – Symbolic wall function in forward form u+ = f(y+), expressed in terms of y_plus. e.g. Reichardt’s law. The inverse form (y+ = g(u+), e.g. Spalding) is not accepted.

  • y_plus – The symbol used for y+ inside wall_fn. (will refactor into sp.Lambda later)

  • query – Field that will store u_tau.

  • velocities – The velocity components (u, v, w), overwritten in place.

  • geometry – Geometry object.

  • nu – Kinematic viscosity.

  • band – Reconstruct only where the wall distance is below this.

  • match_height – Wall distance of the matching point used to infer u_tau. Must exceed band.

  • n_iter – Newton iterations for u_tau.

  • max_dist – Search radius passed to wp.mesh_query_point.

defaults = {'band': 2, 'match_height': 6, 'max_dist': 10.0, 'maxiter': 8, 'side': 1}
doit(deep=False, **hints)

Evaluate objects that are not evaluated by default like limits, integrals, sums and products. All objects of this kind will be evaluated recursively, unless some species were excluded via ‘hints’ or unless the ‘deep’ hint was set to ‘False’.

>>> from sympy import Integral
>>> from sympy.abc import x
>>> 2*Integral(x, x)
2*Integral(x, x)
>>> (2*Integral(x, x)).doit()
x**2
>>> (2*Integral(x, x)).doit(deep=False)
2*Integral(x, x)
wall_fn
y_plus
query
velocities
geometry
nu
band
match_height
maxiter
max_dist
side
default_assumptions = {}
class sofia.ir.While(*args, **kwargs)

Bases: CustomToken

A while-loop.

Repeat a block of code (body) for as long as condition holds. The condition is checked before each iteration, the first one included, so the body never runs if it is false at the outset.

condition should be an expression of BooleanKind involving Scalar variables and fields wrapped in a sofia.field.reductions.Reduce. Every scalar and field the condition reads must already hold a value before the loop, and every reduction is re-evaluated before each check.

With max_iter given, it stops after at most max_iter iterations. The loop exits without raising any exception or warning.

The loop returns the inputs of body together with everything condition reads and, when n_iter is given, also the number of iterations. A variable written only inside the body, and not read by the condition, stays local to it.

Parameters:
  • condition (sympy.core.basic.Basic) – The loop runs for as long as this holds.

  • body (sofia.ir.base.CodeBlock) – Loop body.

  • max_iter (sympy.core.numbers.Integer | sympy.core.symbol.Symbol | None) – Maximum allowed number of iterations. By default it is None, for which the loop runs as long as the condition holds.

  • n_iter (sofia.scalar.scalar.Scalar | None) – Integer Scalar where to store the number of iterations run in the loop. By default it is None, for which the number of iterations is not returned.

Raises:
  • ValueError – If condition is not a boolean expression, if max_iter is a non-positive integer or a symbol assumed to be non-positive, or if n_iter appears in condition or body.

  • TypeError – If max_iter is neither an integer nor a symbol, or n_iter is not an integer Scalar.

  • NotImplementedError – If condition contains fields outside a reduction.

Examples

```python from sofia import Field, Grid, Scalar, ir from sofia.field.reductions import Norm

G = Grid(ndim=3) u = Field(“u”, G) threshold = Scalar(“threshold”) n_iter = Scalar(“n_iter”, dtype=int)

code_ir = ir.CodeBlock(

ir.Inputs((u, threshold)), ir.While(Norm(u) > threshold, ir.CodeBlock(u << u / 2), n_iter=n_iter), ir.Outputs((u, n_iter)),

)

```

condition: Basic
body: CodeBlock
max_iter: Integer | Symbol | None
n_iter: Scalar | None
transform()

Represent this token in terms of other IR tokens.

All returned tokens must (eventually) expand to just tokens defined in ir.base (e.g. ir.base.For), as the code generator is only defined for those tokens. Note that tt is okay for this method to return other custom tokens not from ir.base, as long as these tokens (possibly through multiple .transform() calls) eventually expand to just ir.base tokens.

Subclasses must override this method.

Returns:

The token in terms of other IR tokens.

Return type:

sympy.Basic

default_assumptions = {}
defaults = {'max_iter': None, 'n_iter': None}