Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Addop.is_idempotent property to Monoids that meansop(x, x) == x#388

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
eriknw merged 3 commits intopython-graphblas:mainfromeriknw:monoid_is_idempotent
Feb 18, 2023
Merged
Show file tree
Hide file tree
Changes fromall 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
69 changes: 56 additions & 13 deletionsgraphblas/core/operator.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -416,6 +416,11 @@ def commutes_to(self):
def type2(self):
return self.type

@property
def is_idempotent(self):
"""True if ``monoid(x, x) == x`` for any x."""
return self.parent.is_idempotent


class TypedBuiltinSemiring(TypedOpBase):
__slots__ = ()
Expand DownExpand Up@@ -558,6 +563,7 @@ def __init__(self, parent, name, type_, return_type, gb_obj, binaryop, identity)

commutes_to = TypedBuiltinMonoid.commutes_to
type2 = TypedBuiltinMonoid.type2
is_idempotent = TypedBuiltinMonoid.is_idempotent
__call__ = TypedBuiltinMonoid.__call__


Expand DownExpand Up@@ -756,10 +762,10 @@ def _deserialize(name, func, anonymous):


class ParameterizedMonoid(ParameterizedUdf):
__slots__ = "binaryop", "identity", "__signature__"
__slots__ = "binaryop", "identity", "_is_idempotent", "__signature__"
is_commutative = True

def __init__(self, name, binaryop, identity, *, anonymous=False):
def __init__(self, name, binaryop, identity, *,is_idempotent=False,anonymous=False):
if type(binaryop) is not ParameterizedBinaryOp:
raise TypeError("binaryop must be parameterized")
self.binaryop = binaryop
Expand All@@ -776,6 +782,7 @@ def __init__(self, name, binaryop, identity, *, anonymous=False):
f" identity{sig}"
)
self.identity = identity
self._is_idempotent = is_idempotent
if name is None:
name = binaryop.name
super().__init__(name, anonymous)
Expand All@@ -788,10 +795,17 @@ def _call(self, *args, **kwargs):
identity = self.identity
if callable(identity):
identity = identity(*args, **kwargs)
return Monoid.register_anonymous(binary, identity, self.name)
return Monoid.register_anonymous(
binary, identity, self.name, is_idempotent=self._is_idempotent
)

commutes_to = TypedBuiltinMonoid.commutes_to

@property
def is_idempotent(self):
"""True if ``monoid(x, x) == x`` for any x."""
return self._is_idempotent

def __reduce__(self):
name = f"monoid.{self.name}"
if not self._anonymous and name in _STANDARD_OPERATOR_NAMES: # pragma: no cover
Expand DownExpand Up@@ -2491,7 +2505,7 @@ class Monoid(OpBase):
as well as in the ``graphblas.ops`` combined namespace.
"""

__slots__ = "_binaryop", "_identity"
__slots__ = "_binaryop", "_identity", "_is_idempotent"
is_commutative = True
is_positional = False
_custom_dtype = None
Expand All@@ -2517,12 +2531,14 @@ class Monoid(OpBase):
}

@classmethod
def _build(cls, name, binaryop, identity, *, anonymous=False):
def _build(cls, name, binaryop, identity, *,is_idempotent=False,anonymous=False):
if type(binaryop) is not BinaryOp:
raise TypeError(f"binaryop must be a BinaryOp, not {type(binaryop)}")
if name is None:
name = binaryop.name
new_type_obj = cls(name, binaryop, identity, anonymous=anonymous)
new_type_obj = cls(
name, binaryop, identity, is_idempotent=is_idempotent, anonymous=anonymous
)
if not binaryop._is_udt:
if not isinstance(identity, Mapping):
identities = dict.fromkeys(binaryop.types, identity)
Expand DownExpand Up@@ -2586,7 +2602,7 @@ def _compile_udt(self, dtype, dtype2):
return op

@classmethod
def register_anonymous(cls, binaryop, identity, name=None):
def register_anonymous(cls, binaryop, identity, name=None, *, is_idempotent=False):
"""Register a Monoid without registering it in the ``graphblas.monoid`` namespace.

Because it is not registered in the namespace, the name is optional.
Expand All@@ -2599,17 +2615,21 @@ def register_anonymous(cls, binaryop, identity, name=None):
Identity value of the monoid
name : str, optional
Name associated with the monoid
is_idempotent : bool, default False
Does ``op(x, x) == x`` for any x?

Returns
-------
Function handle
"""
if type(binaryop) is ParameterizedBinaryOp:
return ParameterizedMonoid(name, binaryop, identity, anonymous=True)
return cls._build(name, binaryop, identity, anonymous=True)
return ParameterizedMonoid(
name, binaryop, identity, is_idempotent=is_idempotent, anonymous=True
)
return cls._build(name, binaryop, identity, is_idempotent=is_idempotent, anonymous=True)

@classmethod
def register_new(cls, name, binaryop, identity, *, lazy=False):
def register_new(cls, name, binaryop, identity, *,is_idempotent=False,lazy=False):
"""Register a Monoid. The name will be used to identify the Monoid in the
``graphblas.monoid`` namespace.

Expand All@@ -2624,10 +2644,10 @@ def register_new(cls, name, binaryop, identity, *, lazy=False):
{"name": name, "binaryop": binaryop, "identity": identity},
)
elif type(binaryop) is ParameterizedBinaryOp:
monoid = ParameterizedMonoid(name, binaryop, identity)
monoid = ParameterizedMonoid(name, binaryop, identity, is_idempotent=is_idempotent)
setattr(module, funcname, monoid)
else:
monoid = cls._build(name, binaryop, identity)
monoid = cls._build(name, binaryop, identity, is_idempotent=is_idempotent)
setattr(module, funcname, monoid)
# Also save it to `graphblas.op` if not yet defined
opmodule, funcname = cls._remove_nesting(name, module=op, modname="op", strict=False)
Expand All@@ -2641,10 +2661,11 @@ def register_new(cls, name, binaryop, identity, *, lazy=False):
if not lazy:
return monoid

def __init__(self, name, binaryop=None, identity=None, *, anonymous=False):
def __init__(self, name, binaryop=None, identity=None, *,is_idempotent=False,anonymous=False):
super().__init__(name, anonymous=anonymous)
self._binaryop = binaryop
self._identity = identity
self._is_idempotent = is_idempotent
if binaryop is not None:
binaryop._monoid = self
if binaryop._is_udt:
Expand All@@ -2671,6 +2692,11 @@ def identities(self):
"""The per-dtype identity values for the Monoid."""
return {dtype: val.identity for dtype, val in self._typed_ops.items()}

@property
def is_idempotent(self):
"""True if ``monoid(x, x) == x`` for any x."""
return self._is_idempotent

@property
def _is_udt(self):
return self._binaryop is not None and self._binaryop._is_udt
Expand DownExpand Up@@ -2712,6 +2738,23 @@ def _initialize(cls):
cur_op.types[dtype] = BOOL
cur_op.coercions[dtype] = BOOL
cur_op._typed_ops[dtype] = bool_op

# Builtin monoids that are idempotent; i.e., `op(x, x) == x` for any x
for name in {"any", "band", "bor", "land", "lor", "max", "min"}:
getattr(monoid, name)._is_idempotent = True
for name in {
"bitwise_and",
"bitwise_or",
"fmax",
"fmin",
"gcd",
"logical_and",
"logical_or",
"maximum",
"minimum",
}:
getattr(monoid.numpy, name)._is_idempotent = True

# Allow some functions to work on UDTs
any_ = monoid.any
any_._identity = 0
Expand Down
20 changes: 19 additions & 1 deletiongraphblas/tests/test_op.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -297,6 +297,8 @@ def plus_plus_x_identity(x=0):
assert bin_op.monoid is monoid
assert bin_op(1).monoid is monoid(1)
assert monoid(2) is bin_op(2).monoid
assert not monoid.is_idempotent
assert not monoid(1).is_idempotent
# However, this still fails.
# For this to work, we would need `bin_op1` to know it was created from a
# ParameterizedBinaryOp. It would then need to check to see if the parameterized
Expand DownExpand Up@@ -353,9 +355,12 @@ def bad_identity(x=0):
raise ValueError("hahaha!")

assert bin_op.monoid is None
monoid = Monoid.register_anonymous(bin_op, bad_identity, name="broken_monoid")
monoid = Monoid.register_anonymous(
bin_op, bad_identity, is_idempotent=True, name="broken_monoid"
)
assert bin_op.monoid is monoid
assert bin_op(1).monoid is None
assert monoid.is_idempotent


@pytest.mark.slow
Expand DownExpand Up@@ -1327,3 +1332,16 @@ def test_deprecated():
gb.op.secondj
with pytest.warns(DeprecationWarning, match="please use"):
gb.agg.argmin


def test_is_idempotent():
assert monoid.min.is_idempotent
assert monoid.max[int].is_idempotent
assert monoid.lor.is_idempotent
assert monoid.band.is_idempotent
assert monoid.numpy.gcd.is_idempotent
assert not monoid.plus.is_idempotent
assert not monoid.times[float].is_idempotent
assert not monoid.numpy.equal.is_idempotent
with pytest.raises(AttributeError):
binary.min.is_idempotent
1 change: 1 addition & 0 deletionspyproject.toml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,7 @@ ignore = [
# Intentionally ignored
"COM812", # Trailing comma missing
"D203", # 1 blank line required before class docstring (Note: conflicts with D211, which is preferred)
"D400", # First line should end with a period (Note: prefer D415, which also allows "?" and "!")
"PLR0911", # Too many return statements
"PLR0912", # Too many branches
"PLR0913", # Too many arguments to function call
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp