Skip to content

Commit

Permalink
math operators: percent of, round up, round down, logarithm (#1603)
Browse files Browse the repository at this point in the history
* percent operator for math node

* floor and ceiling operations

* ...

* ...

* logarithm operation

* ...

* remove round up/down

* round node

* ...

* ...

* respond to feedback

* ...
  • Loading branch information
adodge committed Mar 1, 2023
1 parent d87775a commit da6bae0
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 0 deletions.
10 changes: 10 additions & 0 deletions backend/src/nodes/nodes/utility/math_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ class MathOperation(Enum):
MULTIPLY = "mul"
DIVIDE = "div"
POWER = "pow"
LOG = "log"
MAXIMUM = "max"
MINIMUM = "min"
MODULO = "mod"
PERCENT = "percent"


OP_LABEL: Dict[MathOperation, str] = {
Expand All @@ -28,9 +30,11 @@ class MathOperation(Enum):
MathOperation.MULTIPLY: "Multiply: a × b",
MathOperation.DIVIDE: "Divide: a ÷ b",
MathOperation.POWER: "Exponent: a ^ b",
MathOperation.LOG: "Logarithm: log a of b",
MathOperation.MAXIMUM: "Maximum: max(a, b)",
MathOperation.MINIMUM: "Minimum: min(a, b)",
MathOperation.MODULO: "Modulo: a mod b",
MathOperation.PERCENT: "Percent: a × b ÷ 100",
}

_special_mod_numbers = (0.0, float("inf"), float("-inf"), float("nan"))
Expand Down Expand Up @@ -71,9 +75,11 @@ def __init__(self):
MathOperation::Multiply => a * b,
MathOperation::Divide => a / b,
MathOperation::Power => number::pow(a, b),
MathOperation::Log => number::log(a) / number::log(b),
MathOperation::Maximum => max(a, b),
MathOperation::Minimum => min(a, b),
MathOperation::Modulo => number::mod(a, b),
MathOperation::Percent => a * b / 100,
}
""",
)
Expand All @@ -97,6 +103,8 @@ def run(
return a / b
elif op == MathOperation.POWER:
return a**b
elif op == MathOperation.LOG:
return math.log(b, a)
elif op == MathOperation.MAXIMUM:
return max(a, b)
elif op == MathOperation.MINIMUM:
Expand All @@ -106,5 +114,7 @@ def run(
return a - b * math.floor(a / b)
else:
return a % b
elif op == MathOperation.PERCENT:
return a * b / 100
else:
raise RuntimeError(f"Unknown operator {op}")
132 changes: 132 additions & 0 deletions backend/src/nodes/nodes/utility/round.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
from __future__ import annotations

import math
from enum import Enum
from typing import Union

import numpy as np

from ...groups import conditional_group
from ...node_base import NodeBase
from ...node_factory import NodeFactory
from ...properties.inputs import EnumInput, NumberInput
from ...properties.outputs import NumberOutput
from ...utils.utils import round_half_up
from . import category as UtilityCategory


class RoundOperation(Enum):
FLOOR = "Round down"
CEILING = "Round up"
ROUND = "Round"


class RoundScale(Enum):
UNIT = "Integer"
MULTIPLE = "Multiple of..."
POWER = "Power of..."


@NodeFactory.register("chainner:utility:math_round")
class RoundNode(NodeBase):
def __init__(self):
super().__init__()
self.description = "Round an input number"
self.inputs = [
NumberInput(
"Input",
minimum=None,
maximum=None,
precision=100,
controls_step=1,
),
EnumInput(
RoundOperation,
"Operation",
option_labels={k: k.value for k in RoundOperation},
),
EnumInput(
RoundScale,
"To the nearest",
option_labels={k: k.value for k in RoundScale},
),
conditional_group(enum=2, condition=RoundScale.MULTIPLE.value)(
NumberInput(
"Multiple",
default=1,
minimum=1e-100,
maximum=None,
precision=100,
controls_step=1,
)
),
conditional_group(enum=2, condition=RoundScale.POWER.value)(
NumberInput(
"Power",
default=2,
minimum=np.nextafter(1.0, np.inf),
maximum=None,
precision=100,
controls_step=1,
)
),
]
self.outputs = [
NumberOutput(
"Result",
output_type="""
let x = Input0;
let m = Input3;
let p = Input4;
match Input2 {
RoundScale::Unit => match Input1 {
RoundOperation::Floor => floor(x),
RoundOperation::Ceiling => ceil(x),
RoundOperation::Round => round(x),
},
RoundScale::Multiple => match Input1 {
RoundOperation::Floor => floor(x/m) * m,
RoundOperation::Ceiling => ceil(x/m) * m,
RoundOperation::Round => round(x/m) * m,
},
RoundScale::Power => match Input1 {
RoundOperation::Floor => number::pow(p, floor(number::log(x)/number::log(p))),
RoundOperation::Ceiling => number::pow(p, ceil(number::log(x)/number::log(p))),
RoundOperation::Round => number::pow(p, round(number::log(x)/number::log(p))),
},
}
""",
)
]

self.category = UtilityCategory
self.name = "Round"
self.icon = "MdCalculate"
self.sub = "Math"

def run(
self,
a: Union[int, float],
operation: RoundOperation,
scale: RoundScale,
m: Union[int, float],
p: Union[int, float],
) -> Union[int, float]:
if operation == RoundOperation.FLOOR:
op = math.floor
elif operation == RoundOperation.CEILING:
op = math.ceil
elif operation == RoundOperation.ROUND:
op = round_half_up
else:
raise RuntimeError(f"Unknown operation {operation}")

if scale == RoundScale.UNIT:
return op(a)
elif scale == RoundScale.MULTIPLE:
return op(a / m) * m
elif scale == RoundScale.POWER:
return p ** op(math.log(a, p))
else:
raise RuntimeError(f"Unknown scale {scale}")

0 comments on commit da6bae0

Please sign in to comment.