From 1dbd3f076da4cf3fd92723d2fa908baed10ee5ec Mon Sep 17 00:00:00 2001 From: mcDandy Date: Tue, 20 Jan 2026 23:33:24 +0100 Subject: [PATCH] quick fixes --- README.md | 11 +++++++++-- more_math/GuiderMathNode.py | 24 ++++++++++++++++-------- more_math/Parser/UnifiedMathVisitor.py | 23 +++++++++++++++-------- tests/test_guider_math.py | 22 ++++++++++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b6db16e..b63fe6c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ You can also get the node from comfy manager under the name of More math. - functions and variables in math expressions - Conversion between INT and FLOAT; AUDIO and IMAGE (red - real - strenght of cosine of frequency; blue - imaginary - strenght of sine of frequency; green - log1p of amplitude - just so it looks good to humans) -- Nodes for FLOAT, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE and SIGMAS +- Nodes for FLOAT, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE, SIGMAS and GUIDER - Vector Math: Support for List literals `[v1, v2, ...]` and operations between lists/scalars/tensors - Custom functions `funcname(variable,variable,...)->expression;` they can be used in any later defined custom function or in expression. Shadowing inbuilt functions do not work. @@ -185,7 +185,14 @@ You can also get the node from comfy manager under the name of More math. - **NOISE** - refer to `IMAGE and LATENT` for most variables - `I` or `input_latent` – latent used as input to generate noise before noise is generated into it -- **CONDITIONING and FLOAT** +- **GUIDER** + - refer to `IMAGE and LATENT` for visual part (positions, sizes, etc.) + - `sigma` - current sigma value + - `seed` - seed used for noise generation + - `steps` - total number of sampling steps + - `current_step` - current step index (0 to steps) + +- **CONDITIONING, SIGMAS and FLOAT** - no additional variables - **MODEL, CLIP and VAE** - `L` or `layer` - a position of layer from beginning of object diff --git a/more_math/GuiderMathNode.py b/more_math/GuiderMathNode.py index fc4a068..b99d485 100644 --- a/more_math/GuiderMathNode.py +++ b/more_math/GuiderMathNode.py @@ -14,6 +14,9 @@ from .helper_functions import ( from comfy_api.latest import io import comfy.sampler_helpers import comfy.model_patcher +import comfy.utils +import comfy.hooks +import comfy.samplers class GuiderMathNode(io.ComfyNode): @@ -30,7 +33,7 @@ class GuiderMathNode(io.ComfyNode): inputs=[ io.Autogrow.Input(id="G", template=io.Autogrow.TemplatePrefix(io.Guider.Input("guider"), prefix="G", min=1, max=50)), io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)), - io.String.Input(id="Guider", default="G0*(1-F0)+G1*F0", tooltip="Expression to apply on input guiders. Aliases: a=G0, b=G1, c=G2, d=G3, w=F0, x=F1, y=F2, z=F3"), + io.String.Input(id="Guider", default="G0*(1-F0)+G1*F0", tooltip="Expression to apply on input guiders. Aliases: a=G0, b=G1, c=G2, d=G3, w=F0, x=F1, y=F2, z=F3. Context: steps, current_step"), ], outputs=[ io.Guider.Output(), @@ -80,7 +83,10 @@ class MathGuider: self.expression = expression self.tree = parse_expr(expression) self.inner_model = None # Will be set during sample - + self.sigmas = None + self.current_step = 0 + self.steps = 0 + @property def model_patcher(self): # Return the model patcher of the first valid guider @@ -89,12 +95,11 @@ class MathGuider: if g is not None and hasattr(g, "model_patcher"): return g.model_patcher # If no guider has it (e.g. all None or bare wrappers), try to return shared inner model's patcher if available? - # But usually we need it before inner_model is set. + # But usually we need it before inner_model is set. # So we just return None which might fail later if caller doesn't check. return None def __call__(self, x, sigma, model_options={}, seed=None): - print("__call__") # Collect predictions from all guiders g_results = {} for k, guider in self.G.items(): @@ -151,9 +156,10 @@ class MathGuider: "batch_count": eval_samples.shape[0], "N": eval_samples.shape[channel_dim] if channel_dim < ndim else 0, "channel_count": eval_samples.shape[channel_dim] if channel_dim < ndim else 0, - "sigma": sigma, # sigma is scalar or tensor? usually tensor broadcastable - "test_sigma": sigma, + "sigma": sigma.item(), # sigma is scalar or tensor? usually tensor broadcastable "seed": seed if seed is not None else 0, + "steps": self.steps, + "current_step": self.current_step, } # Add dynamic inputs and aliases @@ -173,11 +179,13 @@ class MathGuider: visitor = UnifiedMathVisitor(variables, eval_samples.shape) result_tensor = visitor.visit(self.tree) + self.current_step = self.current_step + 1; # Result should be noise prediction, matching x shape - return as_tensor(result_tensor, eval_samples.shape) + return as_tensor(result_tensor, eval_samples.shape).to(x.device) def sample(self, noise, latent_image, sampler, sigmas, denoise_mask=None, callback=None, disable_pbar=False, seed=None): - print("sample") + self.sigmas = sigmas + self.steps = len(sigmas) if sigmas.shape[-1] == 0: return latent_image diff --git a/more_math/Parser/UnifiedMathVisitor.py b/more_math/Parser/UnifiedMathVisitor.py index 6159288..8d1451a 100644 --- a/more_math/Parser/UnifiedMathVisitor.py +++ b/more_math/Parser/UnifiedMathVisitor.py @@ -10,7 +10,7 @@ class UnifiedMathVisitor(MathExprVisitor): def __init__(self, variables, shape=None, device=None, functions=None): self.variables = variables self.spatial_variables = variables.copy() - self.shape = shape if shape is not None else () + self.shape = shape if shape is not None else (1,) if device is None: self.device = next((v.device for v in variables.values() if isinstance(v, torch.Tensor)), torch.device("cpu")) else: @@ -23,12 +23,16 @@ class UnifiedMathVisitor(MathExprVisitor): def _is_list(self, val): return isinstance(val, (list, tuple)) - def _promote_to_tensor(self, val): + def _promote_to_tensor(self, val,brodcast=False): if self._is_tensor(val): return val.contiguous() if self._is_list(val): return torch.tensor(val, device=self.device) - return torch.broadcast_to(torch.tensor(val, device=self.device), self.shape).contiguous() + if brodcast: + t = list(self.shape) + t[0]=1 + return torch.full(t,val,device=self.device) + return torch.tensor(val, device=self.device) def _bin_op(self, a, b, torch_op, scalar_op): """ @@ -476,7 +480,7 @@ class UnifiedMathVisitor(MathExprVisitor): promoted = [self._promote_to_tensor(x) for x in vals] if len(promoted) == 1: return torch.min(promoted[0]) - return torch.min(torch.stack(torch.broadcast_tensors(*promoted))) + return torch.min(torch.stack(torch.broadcast_tensors(*promoted))).item() def visitSMaxFunc(self, ctx): args = [self.visit(e) for e in ctx.expr()] @@ -486,7 +490,7 @@ class UnifiedMathVisitor(MathExprVisitor): return args[0] if self._is_list(args[0]): return max(args[0]) # max of list - return torch.max(args[0]) # Global max of single tensor + return torch.max(args[0]).item() # Global max of single tensor # Multiple args if all(not self._is_tensor(x) and not self._is_list(x) for x in args): @@ -1047,10 +1051,13 @@ class UnifiedMathVisitor(MathExprVisitor): def visitAppendFunc(self, ctx): a = self.visit(ctx.expr(0)) b = self.visit(ctx.expr(1)) - + if(self._is_tensor(a) and a.numel()==1): + a = a.Item() + if(self._is_tensor(b) and b.numel()==1): + b = b.Item() if self._is_tensor(a) or self._is_tensor(b): - a = self._promote_to_tensor(a) - b = self._promote_to_tensor(b) + a = self._promote_to_tensor(a,True) + b = self._promote_to_tensor(b,True) if a.ndim == 0: a = a.unsqueeze(0) if b.ndim == 0: b = b.unsqueeze(0) return torch.cat((a, b), dim=0) diff --git a/tests/test_guider_math.py b/tests/test_guider_math.py index 07c1b97..abb7061 100644 --- a/tests/test_guider_math.py +++ b/tests/test_guider_math.py @@ -75,5 +75,27 @@ class TestMathGuider(unittest.TestCase): math_guider = MathGuider(G, F, "G0") self.assertIsNone(math_guider.model_patcher) + def test_math_guider_steps_context(self): + # Mock sigmas: [10.0, 5.0, 0.0] -> 2 steps + sigmas = torch.tensor([10.0, 5.0, 0.0]) + g0 = MockGuider(1.0) + G = {"G0": g0} + + math_guider = MathGuider(G, {}, "current_step / steps") + math_guider.sigmas = sigmas # sets sigmas directly for testing + + # Step 0: sigma = 10.0 + x = torch.zeros((1, 1, 1, 1)) + res0 = math_guider(x, torch.tensor(10.0)) + self.assertTrue(torch.allclose(res0, torch.tensor(0.0 / 2.0))) + + # Step 1: sigma = 5.0 + res1 = math_guider(x, torch.tensor(5.0)) + self.assertTrue(torch.allclose(res1, torch.tensor(1.0 / 2.0))) + + # Intermediate sigma should find closest + res_near = math_guider(x, torch.tensor(4.8)) + self.assertTrue(torch.allclose(res_near, torch.tensor(1.0 / 2.0))) + if __name__ == '__main__': unittest.main()