Skip to content

Commit

Permalink
Merge pull request #940 from Li-Xiang-Ideal/master
Browse files Browse the repository at this point in the history
Fix Outer for SparseArray
  • Loading branch information
rocky authored Nov 27, 2023
2 parents 69664c1 + daf7d6d commit 2843011
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 144 deletions.
1 change: 1 addition & 0 deletions SYMBOLS_MANIFEST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ System`LessEqual
System`LetterCharacter
System`LetterNumber
System`LetterQ
System`LeviCivitaTensor
System`Level
System`LevelQ
System`LightBlue
Expand Down
5 changes: 1 addition & 4 deletions mathics/builtin/distance/clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from mathics.eval.nevaluator import eval_N
from mathics.eval.parts import walk_levels
from mathics.eval.tensors import get_default_distance


class _LazyDistances(LazyDistances):
Expand Down Expand Up @@ -139,8 +140,6 @@ def _cluster(self, p, k, mode, evaluation, options, expr):
options, "DistanceFunction", evaluation
)
if distance_function_string == "Automatic":
from mathics.builtin.tensors import get_default_distance

distance_function = get_default_distance(dist_p)
if distance_function is None:
name_of_builtin = strip_context(self.get_name())
Expand Down Expand Up @@ -462,8 +461,6 @@ def eval(
options, "DistanceFunction", evaluation
)
if distance_function_string == "Automatic":
from mathics.builtin.tensors import get_default_distance

distance_function = get_default_distance(dist_p)
if distance_function is None:
evaluation.message(
Expand Down
161 changes: 21 additions & 140 deletions mathics/builtin/tensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,70 +19,17 @@
"""


from sympy.combinatorics import Permutation
from sympy.utilities.iterables import permutations

from mathics.core.atoms import Integer, String
from mathics.core.atoms import Integer
from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED
from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue
from mathics.core.systemsymbols import SymbolRule, SymbolSparseArray
from mathics.eval.parts import get_part


def get_default_distance(p):
if all(q.is_numeric() for q in p):
return Symbol("SquaredEuclideanDistance")
elif all(q.get_head_name() == "System`List" for q in p):
dimensions = [get_dimensions(q) for q in p]
if len(dimensions) < 1:
return None
d0 = dimensions[0]
if not all(d == d0 for d in dimensions[1:]):
return None
if len(dimensions[0]) == 1: # vectors?

def is_boolean(x):
return x.get_head_name() == "System`Symbol" and x in (
SymbolTrue,
SymbolFalse,
)

if all(all(is_boolean(e) for e in q.elements) for q in p):
return Symbol("JaccardDissimilarity")
return Symbol("SquaredEuclideanDistance")
elif all(isinstance(q, String) for q in p):
return Symbol("EditDistance")
else:
from mathics.builtin.colors.color_directives import expression_to_color

if all(expression_to_color(q) is not None for q in p):
return Symbol("ColorDistance")

return None


def get_dimensions(expr, head=None):
if isinstance(expr, Atom):
return []
else:
if head is not None and not expr.head.sameQ(head):
return []
sub_dim = None
sub = []
for element in expr.elements:
sub = get_dimensions(element, expr.head)
if sub_dim is None:
sub_dim = sub
else:
if sub_dim != sub:
sub = []
break
return [len(expr.elements)] + sub
from mathics.eval.tensors import (
eval_Inner,
eval_LeviCivitaTensor,
eval_Outer,
get_dimensions,
)


class ArrayDepth(Builtin):
Expand Down Expand Up @@ -220,46 +167,7 @@ class Inner(Builtin):
def eval(self, f, list1, list2, g, evaluation: Evaluation):
"Inner[f_, list1_, list2_, g_]"

m = get_dimensions(list1)
n = get_dimensions(list2)

if not m or not n:
evaluation.message("Inner", "normal")
return
if list1.get_head() != list2.get_head():
evaluation.message("Inner", "heads", list1.get_head(), list2.get_head())
return
if m[-1] != n[0]:
evaluation.message("Inner", "incom", m[-1], len(m), list1, n[0], list2)
return

head = list1.get_head()
inner_dim = n[0]

def rec(i_cur, j_cur, i_rest, j_rest):
evaluation.check_stopped()
if i_rest:
elements = []
for i in range(1, i_rest[0] + 1):
elements.append(rec(i_cur + [i], j_cur, i_rest[1:], j_rest))
return Expression(head, *elements)
elif j_rest:
elements = []
for j in range(1, j_rest[0] + 1):
elements.append(rec(i_cur, j_cur + [j], i_rest, j_rest[1:]))
return Expression(head, *elements)
else:

def summand(i):
part1 = get_part(list1, i_cur + [i])
part2 = get_part(list2, [i] + j_cur)
return Expression(f, part1, part2)

part = Expression(g, *[summand(i) for i in range(1, inner_dim + 1)])
# cur_expr.elements.append(part)
return part

return rec([], [], m[:-1], n[1:])
return eval_Inner(f, list1, list2, g, evaluation)


class Outer(Builtin):
Expand All @@ -278,10 +186,21 @@ class Outer(Builtin):
Outer product of two matrices:
>> Outer[Times, {{a, b}, {c, d}}, {{1, 2}, {3, 4}}]
= {{{{a, 2 a}, {3 a, 4 a}}, {{b, 2 b}, {3 b, 4 b}}}, {{{c, 2 c}, {3 c, 4 c}}, {{d, 2 d}, {3 d, 4 d}}}}
Outer product of two sparse arrays:
>> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
= SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} -> a c, {1, 2, 2, 1} -> a d, {2, 1, 1, 2} -> b c, {2, 1, 2, 1} -> b d}]
'Outer' of multiple lists:
>> Outer[f, {a, b}, {x, y, z}, {1, 2}]
= {{{f[a, x, 1], f[a, x, 2]}, {f[a, y, 1], f[a, y, 2]}, {f[a, z, 1], f[a, z, 2]}}, {{f[b, x, 1], f[b, x, 2]}, {f[b, y, 1], f[b, y, 2]}, {f[b, z, 1], f[b, z, 2]}}}
'Outer' converts input sparse arrays to lists if f=!=Times, or if the input is a mixture of sparse arrays and lists:
>> Outer[f, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
= {{{{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}, {{f[a, 0], f[a, c]}, {f[a, d], f[a, 0]}}}, {{{f[b, 0], f[b, c]}, {f[b, d], f[b, 0]}}, {{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}}}
>> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], {c, d}]
= {{{0, 0}, {a c, a d}}, {{b c, b d}, {0, 0}}}
Arrays can be ragged:
>> Outer[Times, {{1, 2}}, {{a, b}, {c, d, e}}]
Expand All @@ -304,32 +223,7 @@ class Outer(Builtin):
def eval(self, f, lists, evaluation: Evaluation):
"Outer[f_, lists__]"

lists = lists.get_sequence()
head = None
for list in lists:
if isinstance(list, Atom):
evaluation.message("Outer", "normal")
return
if head is None:
head = list.head
elif not list.head.sameQ(head):
evaluation.message("Outer", "heads", head, list.head)
return

def rec(item, rest_lists, current):
evaluation.check_stopped()
if isinstance(item, Atom) or not item.head.sameQ(head):
if rest_lists:
return rec(rest_lists[0], rest_lists[1:], current + [item])
else:
return Expression(f, *(current + [item]))
else:
elements = []
for element in item.elements:
elements.append(rec(element, rest_lists, current))
return Expression(head, *elements)

return rec(lists[0], lists[1:], [])
return eval_Outer(f, lists, evaluation)


class RotationTransform(Builtin):
Expand Down Expand Up @@ -548,17 +442,4 @@ class LeviCivitaTensor(Builtin):
def eval(self, d, type, evaluation: Evaluation):
"LeviCivitaTensor[d_Integer, type_]"

if isinstance(d, Integer) and type == SymbolSparseArray:
d = d.get_int_value()
perms = list(permutations([i for i in range(1, d + 1)]))
rules = [
Expression(
SymbolRule,
from_python(p),
from_python(Permutation.from_sequence(p).signature()),
)
for p in perms
]
return Expression(
SymbolSparseArray, from_python(rules), from_python([d] * d)
)
return eval_LeviCivitaTensor(d, type)
Loading

0 comments on commit 2843011

Please sign in to comment.