Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outer works improperly on SparseArray #939

Closed
Li-Xiang-Ideal opened this issue Nov 24, 2023 · 0 comments · Fixed by #940
Closed

Outer works improperly on SparseArray #939

Li-Xiang-Ideal opened this issue Nov 24, 2023 · 0 comments · Fixed by #940

Comments

@Li-Xiang-Ideal
Copy link
Contributor

Li-Xiang-Ideal commented Nov 24, 2023

Description

Outer of SparseArrays should gives outer product of them as arrays instead of as expressions with elements Automatic, dims, val, rules.

How to Reproduce

Enter Outer[Times, SparseArray[Automatic, {2, 2}, 0, {{1, 2} -> 1, {2, 1} -> -1}], SparseArray[Automatic, {2, 2}, 0, {{1, 2} -> 1, {2, 1} -> -1}]]

Output Given

SparseArray[SparseArray[Automatic^2, {2 Automatic, 2 Automatic}, 0, {Automatic ({1, 2} -> 1), Automatic ({2, 1} -> -1)}], SparseArray[{2 Automatic, 2 Automatic}, {4, 4}, {0, 0}, {2 ({1, 2} -> 1), 2 ({2, 1} -> -1)}], SparseArray[0, {0, 0}, 0, {0, 0}], SparseArray[{Automatic ({1, 2} -> 1), Automatic ({2, 1} -> -1)}, {2 ({1, 2} -> 1), 2 ({2, 1} -> -1)}, {0, 0}, {({1, 2} -> 1)^2, ({2, 1} \-> -1)^2}]]

Expected behavior

SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} -> 1, {1, 2, 2, 1} -> -1, {2, 1, 1, 2} -> -1, {2, 1, 2, 1} -> 1}]

Your Environment

Mathics v6.0.3, Python 3.11

How to fix it

I will put in a PR to fix it in one day or two after double check. See latest update of my fork or the following code.

btw I hope someone can tell me how to make rules in Outer prior to eval() so I can delete the annoying /; Or[SameQ[f, Times], Not[MemberQ[{lists}, _SparseArray]]].

class Outer(Builtin):
    """
    <url>:Outer product:https://en.wikipedia.org/wiki/Outer_product</url> \
    (<url>:WMA link: https://reference.wolfram.com/language/ref/Outer.html</url>)

    <dl>
      <dt>'Outer[$f$, $x$, $y$]'
      <dd>computes a generalised outer product of $x$ and $y$, using the function $f$ in place of multiplication.
    </dl>

    >> Outer[f, {a, b}, {1, 2, 3}]
     = {{f[a, 1], f[a, 2], f[a, 3]}, {f[b, 1], f[b, 2], f[b, 3]}}

    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' 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]}}}

    Arrays can be ragged:
    >> Outer[Times, {{1, 2}}, {{a, b}, {c, d, e}}]
     = {{{{a, b}, {c, d, e}}, {{2 a, 2 b}, {2 c, 2 d, 2 e}}}}

    Word combinations:
    >> Outer[StringJoin, {"", "re", "un"}, {"cover", "draw", "wind"}, {"", "ing", "s"}] // InputForm
     = {{{"cover", "covering", "covers"}, {"draw", "drawing", "draws"}, {"wind", "winding", "winds"}}, {{"recover", "recovering", "recovers"}, {"redraw", "redrawing", "redraws"}, {"rewind", "rewinding", "rewinds"}}, {{"uncover", "uncovering", "uncovers"}, {"undraw", "undrawing", "undraws"}, {"unwind", "unwinding", "unwinds"}}}

    Compositions of trigonometric functions:
    >> trigs = Outer[Composition, {Sin, Cos, Tan}, {ArcSin, ArcCos, ArcTan}]
     = {{Composition[Sin, ArcSin], Composition[Sin, ArcCos], Composition[Sin, ArcTan]}, {Composition[Cos, ArcSin], Composition[Cos, ArcCos], Composition[Cos, ArcTan]}, {Composition[Tan, ArcSin], Composition[Tan, ArcCos], Composition[Tan, ArcTan]}}
    Evaluate at 0:
    >> Map[#[0] &, trigs, {2}]
     = {{0, 1, 0}, {1, 0, 1}, {0, ComplexInfinity, 0}}
    """

    rules = {
        "Outer[f_, a___, b_SparseArray, c___] /; UnsameQ[f, Times]": "Outer[f, a, b // Normal, c]",
    }

    summary_text = "generalized outer product"

    def eval(self, f, lists, evaluation: Evaluation):
        "Outer[f_, lists__] /; Or[SameQ[f, Times], Not[MemberQ[{lists}, _SparseArray]]]"

        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)

        def rec_sparse(item, rest_lists, current):
            evaluation.check_stopped()
            if isinstance(item, tuple):  # (rules)
                elements = []
                for element in item:
                    elements.extend(rec_sparse(element, rest_lists, current))
                return tuple(elements)
            else:  # rule
                _pos, _val = item.elements
                if rest_lists:
                    return rec_sparse(
                        rest_lists[0],
                        rest_lists[1:],
                        (current[0] + _pos.elements, current[1] * _val),
                    )
                else:
                    return (
                        Expression(
                            SymbolRule,
                            ListExpression(*(current[0] + _pos.elements)),
                            current[1] * _val,
                        ),
                    )

        if head.sameQ(SymbolSparseArray):
            dims = []
            val = Integer1
            data = []  # data = [(rules), ...]
            for _list in lists:
                dims.extend(_list.elements[1])
                val *= _list.elements[2]
                if _list.elements[2] == Integer0:  # _val==0
                    data.append(_list.elements[3].elements)  # append (rules)
                else:  # _val!=0, append (rules, other pos->_val)
                    other_pos = []
                    for pos in itertools.product(
                        *(range(1, d.value + 1) for d in _list.elements[1])
                    ):
                        other_pos.append(
                            ListExpression(*(Integer(i) for i in pos))
                        )  # generate all pos
                    rules_pos = set(
                        rule.elements[0] for rule in _list.elements[3].elements
                    )  # pos of existing rules
                    other_pos = (
                        set(other_pos) - rules_pos
                    )  # remove pos of existing rules
                    other_rules = []
                    for pos in other_pos:
                        other_rules.append(
                            Expression(SymbolRule, pos, _list.elements[2])
                        )  # generate other pos->_val
                    data.append(
                        _list.elements[3].elements + tuple(other_rules)
                    )  # append (rules, other pos->_val)
            dims = ListExpression(*dims)
            return Expression(
                SymbolSparseArray,
                SymbolAutomatic,
                dims,
                val,
                ListExpression(*rec_sparse(data[0], data[1:], ((), Integer1))),
            )
        else:
            return rec(lists[0], lists[1:], [])
@Li-Xiang-Ideal Li-Xiang-Ideal linked a pull request Dec 4, 2023 that will close this issue
@Li-Xiang-Ideal Li-Xiang-Ideal linked a pull request Dec 4, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant