Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "sympde"
version = "0.19.2"
version = "0.19.3"
description = "Symbolic calculus for partial differential equations (and variational forms)"
readme = "README.rst"
requires-python = ">= 3.9"
Expand Down
149 changes: 93 additions & 56 deletions sympde/expr/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def __new__(cls, arguments, expr, **options):

args = _sanitize_arguments(arguments, is_linear=True)

if not is_linear_expression(expr, args, integral=False):
if not is_linear_expression(expr, args):
msg = '> Expression is not linear'
raise UnconsistentLinearExpressionError(msg)

Expand Down Expand Up @@ -739,71 +739,108 @@ def linearize(form, fields, trials=None):
return BilinearForm((trials, tests), bilinear_expr)

#==============================================================================
def is_linear_expression(expr, args, integral=True, debug=True):
"""checks if an expression is linear with respect to the given arguments."""
# ...
left_args = []
right_args = []
def is_linear_expression(expr, args, debug=True):
"""
Checks if expression is linear with respect to each argument in ``args``:

1. Additivity: f(x + y) = f(x) + f(y)
2. Homogeneity: f(alpha * x) = alpha * f(x)

In general, `f` may have an arbitrary number of arguments: `x` and `y`
represent independent copies of the same list of arguments.


Parameters
----------
expr : Expr
Symbolic expression to test.
args : iterable
Arguments with respect to which expr is tested for linearity.
Only one instance of each argument needs to be provided; the function
internally creates the independent copies required for the additivity
and homogeneity checks.
Each argument must be ScalarFunction or VectorFunction.
Comment thread
yguclu marked this conversation as resolved.
debug : bool, optional
Print diagnostic info if a check fails.

Returns
-------
bool
True if the expression is linear with respect to all given arguments,
False otherwise.
"""
assert isinstance(expr, Expr)

x_args = []
y_args = []

# create 2 independent copies (x and y) of every original argument
for arg in args:
tag = random_string( 4 )
tag = random_string(4)

if isinstance(arg, ScalarFunction):
left = ScalarFunction(arg.space, name='l_' + tag)
right = ScalarFunction(arg.space, name='r_' + tag)
x = ScalarFunction(arg.space, name='x_' + tag)
y = ScalarFunction(arg.space, name='y_' + tag)

elif isinstance(arg, VectorFunction):
left = VectorFunction(arg.space, name='l_' + tag)
right = VectorFunction(arg.space, name='r_' + tag)
x = VectorFunction(arg.space, name='x_' + tag)
y = VectorFunction(arg.space, name='y_' + tag)
else:
raise TypeError('argument must be a {Scalar|Vector}Function')

left_args += [left]
right_args += [right]
# ...

# ... check addition
newargs = [left + right for left, right in zip(left_args, right_args)]

newexpr = expr.subs(zip(args, newargs))
left_expr = expr.subs(zip(args, left_args))
right_expr = expr.subs(zip(args, right_args))

a = newexpr
b = left_expr + right_expr

if not( (a-b).expand() == 0 or a.expand() == b.expand()):
# TODO use a warning or exception?
if debug:
print('Failed to assert addition property')
print('{} != {}'.format(a.expand(), b.expand()))
return False

# ...

# ... check multiplication
tag = random_string( 4 )
coeff = Constant('alpha_' + tag)

newexpr = expr
for arg, left in zip(args, left_args):
newarg = coeff * left
newexpr = newexpr.subs(arg, newarg)

atoms = list(newexpr.atoms(BasicOperator))
x_args.append(x)
y_args.append(y)

# ------------------------------------------------------------------------
# check addition property: f(x + y) = f(x) + f(y)
summed_args = [x + y for x, y in zip(x_args, y_args)]
expr_at_x = expr.subs(zip(args, x_args)) # f(x)
expr_at_y = expr.subs(zip(args, y_args)) # f(y)
expr_at_sum = expr.subs(zip(args, summed_args)) # f(x + y)
expected_sum = expr_at_x + expr_at_y # f(x) + f(y)

if (expr_at_sum - expected_sum).expand() != 0:
expr1 = expr_at_sum.expand()
expr2 = expected_sum.expand()
if expr1 != expr2:
if debug:
print(r"Failed to assert addition property "
r"`f(x + y) = f(x) + f(y)`, where:")
print()
print('f(x + y) =')
print(expr1)
print()
print('f(x) + f(y) =')
print(expr2)
return False

# ------------------------------------------------------------------------
# check multiplication property: f(alpha * x) = alpha * f(x)
alpha = Constant(f"alpha_{random_string(4)}")

scaled_x_args = [alpha * x for x in x_args]
expr_at_scaled_x = expr.subs(zip(args, scaled_x_args))

atoms = list(expr_at_scaled_x.atoms(BasicOperator))
subs = [e.func(*e.args, evaluate=True) for e in atoms]
newexpr = newexpr.subs(zip(atoms, subs))


left_expr = expr.subs(list(zip(args, left_args)))
left_expr = coeff * left_expr
if not( (newexpr-left_expr).expand() == 0 or newexpr.expand()==left_expr.expand()):
# TODO use a warning or exception?
if debug:
print('Failed to assert multiplication property')
print('{} != {}'.format(newexpr, left_expr))
return False
# ...
expr_at_scaled_x = expr_at_scaled_x.subs(zip(atoms, subs))

scaled_expr = alpha * expr_at_x

if (expr_at_scaled_x - scaled_expr).expand() != 0:
expr1 = expr_at_scaled_x.expand()
expr2 = scaled_expr.expand()
if expr1 != expr2:
if debug:
print(r"Failed to assert multiplication property "
r"`f(alpha * x) = alpha * f(x)`, where:")
print()
print('f(alpha * x) =')
print(expr1)
print()
print('alpha * f(x) =')
print(expr2)
return False

return True

Expand Down
Loading