diff --git a/backend/src/nodes/nodes/utility/math_node.py b/backend/src/nodes/nodes/utility/math_node.py index 6a88e4821..d5d918deb 100644 --- a/backend/src/nodes/nodes/utility/math_node.py +++ b/backend/src/nodes/nodes/utility/math_node.py @@ -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] = { @@ -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")) @@ -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, } """, ) @@ -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: @@ -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}") diff --git a/backend/src/nodes/nodes/utility/round.py b/backend/src/nodes/nodes/utility/round.py new file mode 100644 index 000000000..e9d51ffb3 --- /dev/null +++ b/backend/src/nodes/nodes/utility/round.py @@ -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}")