refactor: renamed LazyValueFunc to Func

main
Sofus Albert Høgsbro Rose 2024-05-21 09:04:05 +02:00
parent 84825c2642
commit dccf952ad3
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
20 changed files with 141 additions and 151 deletions

View File

@ -48,7 +48,7 @@ from .flow_kinds import (
FlowKind,
InfoFlow,
RangeFlow,
LazyValueFuncFlow,
FuncFlow,
ParamsFlow,
ScalingMode,
ValueFlow,
@ -119,7 +119,7 @@ __all__ = [
'FlowKind',
'InfoFlow',
'RangeFlow',
'LazyValueFuncFlow',
'FuncFlow',
'ParamsFlow',
'ScalingMode',
'ValueFlow',

View File

@ -19,7 +19,7 @@ from .capabilities import CapabilitiesFlow
from .flow_kinds import FlowKind
from .info import InfoFlow
from .lazy_range import RangeFlow, ScalingMode
from .lazy_value_func import LazyValueFuncFlow
from .lazy_func import FuncFlow
from .params import ParamsFlow
from .value import ValueFlow
@ -30,7 +30,7 @@ __all__ = [
'InfoFlow',
'RangeFlow',
'ScalingMode',
'LazyValueFuncFlow',
'FuncFlow',
'ParamsFlow',
'ValueFlow',
]

View File

@ -40,7 +40,7 @@ class FlowKind(enum.StrEnum):
Array: An object with dimensions, and possibly a unit.
Whenever a `Value` is defined, a single-element `list` will also be generated by default as `Array`
However, for any other array-like variants (or sockets that only represent array-like objects), `Array` should be defined manually.
LazyValueFunc: A composable function.
Func: A composable function.
Can be used to represent computations for which all data is not yet known, or for which just-in-time compilation can drastically increase performance.
Range: An object that generates an `Array` from range information (start/stop/step/spacing).
This should be used instead of `Array` whenever possible.
@ -55,7 +55,7 @@ class FlowKind(enum.StrEnum):
Array = enum.auto()
# Lazy
LazyValueFunc = enum.auto()
Func = enum.auto()
Range = enum.auto()
# Auxiliary
@ -96,7 +96,7 @@ class FlowKind(enum.StrEnum):
return {
FlowKind.Value: FlowKind.Value,
FlowKind.Array: FlowKind.Array,
FlowKind.LazyValueFunc: FlowKind.LazyValueFunc,
FlowKind.Func: FlowKind.Func,
FlowKind.Range: FlowKind.Range,
}[self]
@ -106,7 +106,7 @@ class FlowKind(enum.StrEnum):
FlowKind.Value: 'CIRCLE',
FlowKind.Array: 'SQUARE',
FlowKind.Range: 'SQUARE',
FlowKind.LazyValueFunc: 'DIAMOND',
FlowKind.Func: 'DIAMOND',
}[self]
####################
@ -119,7 +119,7 @@ class FlowKind(enum.StrEnum):
FlowKind.Value: 'Value',
FlowKind.Array: 'Array',
FlowKind.Range: 'Range',
FlowKind.LazyValueFunc: 'Func',
FlowKind.Func: 'Func',
FlowKind.Params: 'Parameters',
FlowKind.Info: 'Information',
}[v]

View File

@ -30,7 +30,7 @@ LazyFunction: typ.TypeAlias = typ.Callable[[typ.Any, ...], typ.Any]
@dataclasses.dataclass(frozen=True, kw_only=True)
class LazyValueFuncFlow:
class FuncFlow:
r"""Defines a flow of data as incremental function composition.
For specific math system usage instructions, please consult the documentation of relevant nodes.
@ -44,9 +44,9 @@ class LazyValueFuncFlow:
- **Performant**: Since no operations are happening, the UI feels fast and snappy.
## Strongly Related FlowKinds
For doing math, `LazyValueFunc` relies on two other `FlowKind`s, which must run in parallel:
For doing math, `Func` relies on two other `FlowKind`s, which must run in parallel:
- `FlowKind.Info`: Tracks the name, `spux.MathType`, unit (if any), length, and index coordinates for the raw data object produced by `LazyValueFunc`.
- `FlowKind.Info`: Tracks the name, `spux.MathType`, unit (if any), length, and index coordinates for the raw data object produced by `Func`.
- `FlowKind.Params`: Tracks the particular values of input parameters to the lazy function, each of which can also be symbolic.
For more, please see the documentation for each.
@ -84,12 +84,12 @@ class LazyValueFuncFlow:
## 'A0', 'KV0' are of length 'p' and 'q'
def f_0(*args, **kwargs): ...
lazy_value_func_0 = LazyValueFuncFlow(
lazy_func_0 = FuncFlow(
func=f_0,
func_args=[(a_i, type(a_i)) for a_i in A0],
func_kwargs={k: v for k,v in KV0},
)
output_0 = lazy_value_func.func(*A0_computed, **KV0_computed)
output_0 = lazy_func.func(*A0_computed, **KV0_computed)
```
## `depth>0`: Composition Chaining
@ -120,7 +120,7 @@ class LazyValueFuncFlow:
## 'A1', 'KV1' are therefore of length 'r' and 's'
def f_1(output_0, *args, **kwargs): ...
lazy_value_func_1 = lazy_value_func_0.compose_within(
lazy_func_1 = lazy_func_0.compose_within(
enclosing_func=f_1,
enclosing_func_args=[(a_i, type(a_i)) for a_i in A1],
enclosing_func_kwargs={k: type(v) for k,v in K1},
@ -128,10 +128,10 @@ class LazyValueFuncFlow:
A_computed = A0_computed + A1_computed
KW_computed = KV0_computed + KV1_computed
output_1 = lazy_value_func_1.func(*A_computed, **KW_computed)
output_1 = lazy_func_1.func(*A_computed, **KW_computed)
```
By using `LazyValueFunc`, we've guaranteed that even hugely deep $n$s won't ever look more complicated than this.
By using `Func`, we've guaranteed that even hugely deep $n$s won't ever look more complicated than this.
## `max depth`: "Realization"
So, we've composed a bunch of functions of functions of ...
@ -142,22 +142,22 @@ class LazyValueFuncFlow:
```python
# A_all and KW_all must be tracked on the side.
output_n = lazy_value_func_n.func(*A_all, **KW_all)
output_n = lazy_func_n.func(*A_all, **KW_all)
```
Of course, this comes with enormous overhead.
Aside from the function calls themselves (which can be non-trivial), we must also contend with the enormous inefficiency of performing array operations sequentially.
That brings us to the killer feature of `LazyValueFuncFlow`, and the motivating reason for doing any of this at all:
That brings us to the killer feature of `FuncFlow`, and the motivating reason for doing any of this at all:
```python
output_n = lazy_value_func_n.func_jax(*A_all, **KW_all)
output_n = lazy_func_n.func_jax(*A_all, **KW_all)
```
What happened was, **the entire pipeline** was compiled, optimized, and computed with bare-metal performance on either a CPU, GPU, or TPU.
With the help of the `jax` library (and its underlying OpenXLA bytecode), all of that inefficiency has been optimized based on _what we're trying to do_, not _exactly how we're doing it_, in order to maximize the use of modern massively-parallel devices.
See the documentation of `LazyValueFunc.func_jax()` for more information on this process.
See the documentation of `Func.func_jax()` for more information on this process.
@ -187,12 +187,12 @@ class LazyValueFuncFlow:
As a more nuanced example, when lag occurs due to the computing an image-based plot based on live-computed math, then the visual feedback of _the plot actually changing_ seems to have a similar effect, not least because it's emotionally well-understood that detaching the `Viewer` node would also remove the lag.
In short: Even if lazy evaluation didn't make any math faster, it will still _feel_ faster (to a point - raw performance obviously still matters).
Without `LazyValueFuncFlow`, the point of evaluation cannot be chosen at all, which is a huge issue for all the named reasons.
With `LazyValueFuncFlow`, better-chosen evaluation points can be chosen to cause the _user experience_ of high performance, simply because we were able to shift the exact same computation to a point in time where the user either understands or tolerates the delay better.
Without `FuncFlow`, the point of evaluation cannot be chosen at all, which is a huge issue for all the named reasons.
With `FuncFlow`, better-chosen evaluation points can be chosen to cause the _user experience_ of high performance, simply because we were able to shift the exact same computation to a point in time where the user either understands or tolerates the delay better.
## Flexibility
Large-scale math is done on tensors, whether one knows (or likes!) it or not.
To this end, the indexed arrays produced by `LazyValueFuncFlow.func_jax` aren't quite sufficient for most operations we want to do:
To this end, the indexed arrays produced by `FuncFlow.func_jax` aren't quite sufficient for most operations we want to do:
- **Naming**: What _is_ each axis?
Unnamed index axes are sometimes easy to decode, but in general, names have an unexpectedly critical function when operating on arrays.
@ -206,13 +206,13 @@ class LazyValueFuncFlow:
Not only do we endeavor to track these, but we also introduce unit-awareness to the coordinates, and design the entire math system to visually communicate the state of arrays before/after every single computation, as well as only expose operations that this tracked data indicates possible.
In practice, this happens in `FlowKind.Info`, which due to having its own `FlowKind` "lane" can be adjusted without triggering changes to (and therefore recompilation of) the `FlowKind.LazyValueFunc` chain.
In practice, this happens in `FlowKind.Info`, which due to having its own `FlowKind` "lane" can be adjusted without triggering changes to (and therefore recompilation of) the `FlowKind.Func` chain.
**Please consult the `InfoFlow` documentation for more**.
## Performance
All values introduced while processing are kept in a seperate `FlowKind` lane, with its own incremental caching: `FlowKind.Params`.
It's a simple mechanism, but for the cost of introducing an extra `FlowKind` "lane", all of the values used to process data can be live-adjusted without the overhead of recompiling the entire `LazyValueFunc` every time anything changes.
It's a simple mechanism, but for the cost of introducing an extra `FlowKind` "lane", all of the values used to process data can be live-adjusted without the overhead of recompiling the entire `Func` every time anything changes.
Moreover, values used to process data don't even have to be numbers yet: They can be expressions of symbolic variables, complete with units, which are only realized at the very end of the chain, by the node that absolutely cannot function without the actual numerical data.
See the `ParamFlow` documentation for more information.
@ -224,7 +224,7 @@ class LazyValueFuncFlow:
A few teasers of what nodes can do with this system:
**Auto-Differentiation**: `jax.jit` isn't even really the killer feature of `jax`.
`jax` can automatically differentiate `LazyValueFuncFlow.func_jax` with respect to any input parameter, including for fwd/bck jacobians/hessians, with robust numerical stability.
`jax` can automatically differentiate `FuncFlow.func_jax` with respect to any input parameter, including for fwd/bck jacobians/hessians, with robust numerical stability.
When used in
**Symbolic Interop**: Any `sympy` expression containing symbolic variables can be compiled, by `sympy`, into a `jax`-compatible function which takes
We make use of this in the `Expr` socket, enabling true symbolic math to be used in high-performance lazy `jax` computations.
@ -304,7 +304,7 @@ class LazyValueFuncFlow:
if self.supports_jax:
return jax.jit(self.func)
msg = 'Can\'t express LazyValueFuncFlow as JAX function (using jax.jit), since "self.supports_jax" is False'
msg = 'Can\'t express FuncFlow as JAX function (using jax.jit), since "self.supports_jax" is False'
raise ValueError(msg)
####################
@ -317,7 +317,7 @@ class LazyValueFuncFlow:
enclosing_func_kwargs: dict[str, type] = MappingProxyType({}),
supports_jax: bool = False,
) -> typ.Self:
"""Compose `self.func` within the given enclosing function, which itself takes arguments, and create a new `LazyValueFuncFlow` to contain it.
"""Compose `self.func` within the given enclosing function, which itself takes arguments, and create a new `FuncFlow` to contain it.
This is the fundamental operation used to "chain" functions together.
@ -328,13 +328,13 @@ class LazyValueFuncFlow:
C = spux.MathType.Complex
x, y = sp.symbols('x y', real=True)
# Prepare "Root" LazyValueFuncFlow w/x,y args
# Prepare "Root" FuncFlow w/x,y args
expr_root = 3*x + y**2 - 100
expr_root_func = sp.lambdify([x, y], expr, 'jax')
func_root = LazyValueFuncFlow(func=expr_root_func, func_args=[R,R], supports_jax=True)
func_root = FuncFlow(func=expr_root_func, func_args=[R,R], supports_jax=True)
# Compose "Enclosing" LazyValueFuncFlow w/z arg
# Compose "Enclosing" FuncFlow w/z arg
r = sp.Symbol('z', real=True)
z = sp.Symbol('z', complex=True)
expr = 10*sp.re(z) / (z + r)
@ -351,7 +351,7 @@ class LazyValueFuncFlow:
Returns:
A lazy function that takes both the enclosed and enclosing arguments, and returns the value of the enclosing function (whose first argument is the output value of the enclosed function).
"""
return LazyValueFuncFlow(
return FuncFlow(
func=lambda *args, **kwargs: enclosing_func(
self.func(
*list(args[: len(self.func_args)]),
@ -373,17 +373,17 @@ class LazyValueFuncFlow:
Generally, `self.func` produces a single array as output (when doing math, at least).
But sometimes (as in the `OperateMathNode`), we need to perform a binary operation between two arrays, like say, $+$.
Without realizing both `LazyValueFuncFlow`s, it's not immediately obvious how one might accomplish this.
Without realizing both `FuncFlow`s, it's not immediately obvious how one might accomplish this.
This overloaded function of the `|` operator (used as `left | right`) solves that problem.
A new `LazyValueFuncFlow` is created, which takes the arguments of both inputs, and which produces a single output value: A 2-tuple, where each element if the output of each function.
A new `FuncFlow` is created, which takes the arguments of both inputs, and which produces a single output value: A 2-tuple, where each element if the output of each function.
Examples:
Consider this illustrative (pseudocode) example:
```python
# Presume a,b are values, and that A,B are their identifiers.
func_1 = LazyValueFuncFlow(func=compute_big_data_1, func_args=[A])
func_2 = LazyValueFuncFlow(func=compute_big_data_2, func_args=[B])
func_1 = FuncFlow(func=compute_big_data_1, func_args=[A])
func_2 = FuncFlow(func=compute_big_data_2, func_args=[B])
f = (func_1 | func_2).compose_within(func=lambda D: D[0] + D[1])
@ -402,7 +402,7 @@ class LazyValueFuncFlow:
Returns:
A lazy function that takes all arguments of both inputs, and returns a 2-tuple containing both output arguments.
"""
return LazyValueFuncFlow(
return FuncFlow(
func=lambda *args, **kwargs: (
self.func(
*list(args[: len(self.func_args)]),

View File

@ -29,7 +29,7 @@ from blender_maxwell.utils import logger
from .array import ArrayFlow
from .flow_kinds import FlowKind
from .lazy_value_func import LazyValueFuncFlow
from .lazy_func import FuncFlow
log = logger.get(__name__)
@ -361,7 +361,7 @@ class RangeFlow:
The ordering of the symbols is identical to `self.symbols`, which is guaranteed to be a deterministically sorted list of symbols.
Returns:
A `LazyValueFuncFlow` that, given the input symbols defined in `self.symbols`,
A `FuncFlow` that, given the input symbols defined in `self.symbols`,
"""
# Compile JAX Functions for Start/End Expressions
## FYI, JAX-in-JAX works perfectly fine.
@ -378,18 +378,18 @@ class RangeFlow:
return gen_array
@functools.cached_property
def as_lazy_value_func(self) -> LazyValueFuncFlow:
"""Creates a `LazyValueFuncFlow` using the output of `self.as_func`.
def as_lazy_func(self) -> FuncFlow:
"""Creates a `FuncFlow` using the output of `self.as_func`.
This is useful for ex. parameterizing the first array in the node graph, without binding an entire computed array.
Notes:
The the function enclosed in the `LazyValueFuncFlow` is identical to the one returned by `self.as_func`.
The the function enclosed in the `FuncFlow` is identical to the one returned by `self.as_func`.
Returns:
A `LazyValueFuncFlow` containing `self.as_func`, as well as appropriate supporting settings.
A `FuncFlow` containing `self.as_func`, as well as appropriate supporting settings.
"""
return LazyValueFuncFlow(
return FuncFlow(
func=self.as_func,
func_args=[(spux.MathType.from_expr(sym)) for sym in self.symbols],
supports_jax=True,
@ -401,7 +401,7 @@ class RangeFlow:
def realize_start(
self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
) -> ArrayFlow | LazyValueFuncFlow:
) -> ArrayFlow | FuncFlow:
return spux.sympy_to_python(
self.start.subs({sym: symbol_values[sym.name] for sym in self.symbols})
)
@ -409,7 +409,7 @@ class RangeFlow:
def realize_stop(
self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
) -> ArrayFlow | LazyValueFuncFlow:
) -> ArrayFlow | FuncFlow:
return spux.sympy_to_python(
self.stop.subs({sym: symbol_values[sym.name] for sym in self.symbols})
)
@ -417,7 +417,7 @@ class RangeFlow:
def realize_step_size(
self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
) -> ArrayFlow | LazyValueFuncFlow:
) -> ArrayFlow | FuncFlow:
raw_step_size = (self.realize_stop() - self.realize_start() + 1) / self.steps
if self.mathtype is spux.MathType.Integer and raw_step_size.is_integer():
@ -427,8 +427,8 @@ class RangeFlow:
def realize(
self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
kind: typ.Literal[FlowKind.Array, FlowKind.LazyValueFunc] = FlowKind.Array,
) -> ArrayFlow | LazyValueFuncFlow:
kind: typ.Literal[FlowKind.Array, FlowKind.Func] = FlowKind.Array,
) -> ArrayFlow | FuncFlow:
"""Apply a function to the bounds, effectively rescaling the represented array.
Notes:
@ -458,8 +458,8 @@ class RangeFlow:
if kind == FlowKind.Array:
return ArrayFlow(values=gen_array(), unit=self.unit, is_sorted=True)
if kind == FlowKind.LazyValueFunc:
return LazyValueFuncFlow(func=gen_array, supports_jax=True)
if kind == FlowKind.Func:
return FuncFlow(func=gen_array, supports_jax=True)
msg = f'Invalid kind: {kind}'
raise TypeError(msg)

View File

@ -69,7 +69,7 @@ class ParamsFlow:
unit_system: spux.UnitSystem,
symbol_values: dict[spux.Symbol, spux.SympyExpr] = MappingProxyType({}),
):
"""Realize the function arguments contained in this `ParamsFlow`, making it ready for insertion into `LazyValueFunc.func()`.
"""Realize the function arguments contained in this `ParamsFlow`, making it ready for insertion into `Func.func()`.
For all `arg`s in `self.func_args`, the following operations are performed:
- **Unit System**: If `arg`
@ -121,7 +121,7 @@ class ParamsFlow:
):
"""Combine two function parameter lists, such that the LHS will be concatenated with the RHS.
Just like its neighbor in `LazyValueFunc`, this effectively combines two functions with unique parameters.
Just like its neighbor in `Func`, this effectively combines two functions with unique parameters.
The next composed function will receive a tuple of two arrays, instead of just one, allowing binary operations to occur.
"""
return ParamsFlow(

View File

@ -318,12 +318,12 @@ class DataFileFormat(enum.StrEnum):
"""Abstraction of a data file format, providing a regularized way of interacting with filesystem data.
Import/export interacts closely with the `Expr` socket's `FlowKind` semantics:
- `FlowKind.LazyValueFunc`: Generally realized on-import/export.
- `FlowKind.Func`: Generally realized on-import/export.
- **Import**: Loading data is generally eager, but memory-mapped file loading would be manageable using this interface.
- **Export**: The function is realized and only the array is inserted into the file.
- `FlowKind.Params`: Generally consumed.
- **Import**: A new, empty `ParamsFlow` object is created.
- **Export**: The `ParamsFlow` is consumed when realizing the `LazyValueFunc`.
- **Export**: The `ParamsFlow` is consumed when realizing the `Func`.
- `FlowKind.Info`: As the most important element, it is kept in an (optional) sidecar metadata file.
- **Import**: The sidecar file is loaded, checked, and used, if it exists. A warning about further processing may show if it doesn't.
- **Export**: The sidecar file is written next to the canonical data file, in such a manner that it can be both read and loaded.

View File

@ -57,9 +57,7 @@ class ExtractDataNode(base.MaxwellSimNode):
}
output_socket_sets: typ.ClassVar = {
'Sim Data': {'Monitor Data': sockets.MaxwellMonitorDataSocketDef()},
'Monitor Data': {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc)
},
'Monitor Data': {'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func)},
}
####################
@ -349,7 +347,7 @@ class ExtractDataNode(base.MaxwellSimNode):
return ct.FlowSignal.FlowPending
####################
# - FlowKind.Array|LazyValueFunc: Monitor Data -> Expr
# - FlowKind.Array|Func: Monitor Data -> Expr
####################
@events.computes_output_socket(
'Expr',
@ -384,16 +382,14 @@ class ExtractDataNode(base.MaxwellSimNode):
@events.computes_output_socket(
# Trigger
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
# Loaded
output_sockets={'Expr'},
output_socket_kinds={'Expr': ct.FlowKind.Array},
output_sockets_optional={'Expr': True},
)
def compute_extracted_data_lazy(
self, output_sockets: dict
) -> ct.LazyValueFuncFlow | None:
"""Declare `Expr:LazyValueFunc` by creating a simple function that directly wraps `Expr:Array`.
def compute_extracted_data_lazy(self, output_sockets: dict) -> ct.FuncFlow | None:
"""Declare `Expr:Func` by creating a simple function that directly wraps `Expr:Array`.
Returns:
The composable function array, if available, else `ct.FlowSignal.FlowPending`.
@ -402,9 +398,7 @@ class ExtractDataNode(base.MaxwellSimNode):
has_output_expr = not ct.FlowSignal.check(output_expr)
if has_output_expr:
return ct.LazyValueFuncFlow(
func=lambda: output_expr.values, supports_jax=True
)
return ct.FuncFlow(func=lambda: output_expr.values, supports_jax=True)
return ct.FlowSignal.FlowPending
@ -441,7 +435,7 @@ class ExtractDataNode(base.MaxwellSimNode):
"""Declare `Data:Info` by manually selecting appropriate axes, units, etc. for each monitor type.
Returns:
Information describing the `Data:LazyValueFunc`, if available, else `ct.FlowSignal.FlowPending`.
Information describing the `Data:Func`, if available, else `ct.FlowSignal.FlowPending`.
"""
monitor_data = input_sockets['Monitor Data']
monitor_data_type = props['monitor_data_type']

View File

@ -265,10 +265,10 @@ class FilterMathNode(base.MaxwellSimNode):
bl_label = 'Filter Math'
input_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
output_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
####################
@ -518,13 +518,13 @@ class FilterMathNode(base.MaxwellSimNode):
current_bl_socket = self.loose_input_sockets.get('Dim')
if (
current_bl_socket is None
or current_bl_socket.active_kind != ct.FlowKind.LazyValueFunc
or current_bl_socket.active_kind != ct.FlowKind.Func
or current_bl_socket.mathtype != spux.MathType.Real
or current_bl_socket.physical_type != spux.PhysicalType.NonPhysical
):
self.loose_input_sockets = {
'Dim': sockets.ExprSocketDef(
active_kind=ct.FlowKind.LazyValueFunc,
active_kind=ct.FlowKind.Func,
mathtype=spux.MathType.Real,
physical_type=spux.PhysicalType.NonPhysical,
show_info_columns=True,
@ -536,28 +536,28 @@ class FilterMathNode(base.MaxwellSimNode):
self.loose_input_sockets = {}
####################
# - FlowKind.Value|LazyValueFunc
# - FlowKind.Value|Func
####################
@events.computes_output_socket(
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
props={'operation', 'dim_0', 'dim_1', 'slice_tuple'},
input_sockets={'Expr'},
input_socket_kinds={'Expr': {ct.FlowKind.LazyValueFunc, ct.FlowKind.Info}},
input_socket_kinds={'Expr': {ct.FlowKind.Func, ct.FlowKind.Info}},
)
def compute_lazy_value_func(self, props: dict, input_sockets: dict):
def compute_lazy_func(self, props: dict, input_sockets: dict):
operation = props['operation']
lazy_value_func = input_sockets['Expr'][ct.FlowKind.LazyValueFunc]
lazy_func = input_sockets['Expr'][ct.FlowKind.Func]
info = input_sockets['Expr'][ct.FlowKind.Info]
has_lazy_value_func = not ct.FlowSignal.check(lazy_value_func)
has_lazy_func = not ct.FlowSignal.check(lazy_func)
has_info = not ct.FlowSignal.check(info)
# Dimension(s)
dim_0 = props['dim_0']
dim_1 = props['dim_1']
if (
has_lazy_value_func
has_lazy_func
and has_info
and operation is not None
and operation.are_dims_valid(info, dim_0, dim_1)
@ -570,7 +570,7 @@ class FilterMathNode(base.MaxwellSimNode):
else None
)
return lazy_value_func.compose_within(
return lazy_func.compose_within(
operation.jax_func(axis_0, axis_1, slice_tuple),
enclosing_func_args=operation.func_args,
supports_jax=True,
@ -594,14 +594,14 @@ class FilterMathNode(base.MaxwellSimNode):
input_sockets={'Expr', 'Dim'},
input_socket_kinds={
'Expr': ct.FlowKind.Info,
'Dim': {ct.FlowKind.LazyValueFunc, ct.FlowKind.Params, ct.FlowKind.Info},
'Dim': {ct.FlowKind.Func, ct.FlowKind.Params, ct.FlowKind.Info},
},
input_sockets_optional={'Dim': True},
)
def compute_info(self, props, input_sockets) -> ct.InfoFlow:
operation = props['operation']
info = input_sockets['Expr']
dim_coords = input_sockets['Dim'][ct.FlowKind.LazyValueFunc]
dim_coords = input_sockets['Dim'][ct.FlowKind.Func]
dim_params = input_sockets['Dim'][ct.FlowKind.Params]
dim_info = input_sockets['Dim'][ct.FlowKind.Info]
dim_symbol = props['set_dim_symbol']

View File

@ -401,7 +401,7 @@ class MapMathNode(base.MaxwellSimNode):
The name and type of the available symbol is clearly shown, and most valid `sympy` expressions that you would expect to work, should work.
Use of expressions generally imposes no performance penalty: Just like the baked-in operations, it is compiled to a high-performance `jax` function.
Thus, it participates in the `ct.FlowKind.LazyValueFunc` composition chain.
Thus, it participates in the `ct.FlowKind.Func` composition chain.
Attributes:
@ -412,10 +412,10 @@ class MapMathNode(base.MaxwellSimNode):
bl_label = 'Map Math'
input_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
output_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
####################
@ -474,7 +474,7 @@ class MapMathNode(base.MaxwellSimNode):
layout.prop(self, self.blfields['operation'], text='')
####################
# - FlowKind.Value|LazyValueFunc
# - FlowKind.Value|Func
####################
@events.computes_output_socket(
'Expr',
@ -497,16 +497,14 @@ class MapMathNode(base.MaxwellSimNode):
@events.computes_output_socket(
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
props={'operation'},
input_sockets={'Expr'},
input_socket_kinds={
'Expr': ct.FlowKind.LazyValueFunc,
'Expr': ct.FlowKind.Func,
},
)
def compute_func(
self, props, input_sockets
) -> ct.LazyValueFuncFlow | ct.FlowSignal:
def compute_func(self, props, input_sockets) -> ct.FuncFlow | ct.FlowSignal:
operation = props['operation']
expr = input_sockets['Expr']

View File

@ -261,12 +261,12 @@ class OperateMathNode(base.MaxwellSimNode):
bl_label = 'Operate Math'
input_sockets: typ.ClassVar = {
'Expr L': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr R': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr L': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
'Expr R': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
output_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(
active_kind=ct.FlowKind.LazyValueFunc, show_info_columns=True
active_kind=ct.FlowKind.Func, show_info_columns=True
),
}
@ -344,7 +344,7 @@ class OperateMathNode(base.MaxwellSimNode):
layout.prop(self, self.blfields['operation'], text='')
####################
# - FlowKind.Value|LazyValueFunc
# - FlowKind.Value|Func
####################
@events.computes_output_socket(
'Expr',
@ -373,12 +373,12 @@ class OperateMathNode(base.MaxwellSimNode):
@events.computes_output_socket(
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
props={'operation'},
input_sockets={'Expr L', 'Expr R'},
input_socket_kinds={
'Expr L': ct.FlowKind.LazyValueFunc,
'Expr R': ct.FlowKind.LazyValueFunc,
'Expr L': ct.FlowKind.Func,
'Expr R': ct.FlowKind.Func,
},
)
def compose_func(self, props: dict, input_sockets: dict):

View File

@ -97,7 +97,7 @@ class ReduceMathNode(base.MaxwellSimNode):
'Data',
props={'active_socket_set', 'operation'},
input_sockets={'Data', 'Axis', 'Reducer'},
input_socket_kinds={'Reducer': ct.FlowKind.LazyValueFunc},
input_socket_kinds={'Reducer': ct.FlowKind.Func},
input_sockets_optional={'Reducer': True},
)
def compute_data(self, props: dict, input_sockets: dict):

View File

@ -258,10 +258,10 @@ class TransformMathNode(base.MaxwellSimNode):
bl_label = 'Transform Math'
input_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
output_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
####################
@ -323,7 +323,7 @@ class TransformMathNode(base.MaxwellSimNode):
layout.prop(self, self.blfields['operation'], text='')
####################
# - Compute: LazyValueFunc / Array
# - Compute: Func / Array
####################
@events.computes_output_socket(
'Expr',
@ -346,16 +346,14 @@ class TransformMathNode(base.MaxwellSimNode):
@events.computes_output_socket(
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
props={'operation'},
input_sockets={'Expr'},
input_socket_kinds={
'Expr': ct.FlowKind.LazyValueFunc,
'Expr': ct.FlowKind.Func,
},
)
def compute_func(
self, props, input_sockets
) -> ct.LazyValueFuncFlow | ct.FlowSignal:
def compute_func(self, props, input_sockets) -> ct.FuncFlow | ct.FlowSignal:
operation = props['operation']
expr = input_sockets['Expr']

View File

@ -209,7 +209,7 @@ class VizNode(base.MaxwellSimNode):
####################
input_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(
active_kind=ct.FlowKind.LazyValueFunc,
active_kind=ct.FlowKind.Func,
default_symbols=[sim_symbols.x],
default_value=2 * sim_symbols.x.sp_symbol,
),
@ -370,7 +370,7 @@ class VizNode(base.MaxwellSimNode):
props={'viz_mode', 'viz_target', 'colormap'},
input_sockets={'Expr'},
input_socket_kinds={
'Expr': {ct.FlowKind.LazyValueFunc, ct.FlowKind.Info, ct.FlowKind.Params}
'Expr': {ct.FlowKind.Func, ct.FlowKind.Info, ct.FlowKind.Params}
},
all_loose_input_sockets=True,
)
@ -382,7 +382,7 @@ class VizNode(base.MaxwellSimNode):
props={'viz_mode', 'viz_target', 'colormap'},
input_sockets={'Expr'},
input_socket_kinds={
'Expr': {ct.FlowKind.LazyValueFunc, ct.FlowKind.Info, ct.FlowKind.Params}
'Expr': {ct.FlowKind.Func, ct.FlowKind.Info, ct.FlowKind.Params}
},
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
all_loose_input_sockets=True,
@ -392,7 +392,7 @@ class VizNode(base.MaxwellSimNode):
self, managed_objs, props, input_sockets, loose_input_sockets, unit_systems
):
# Retrieve Inputs
lazy_value_func = input_sockets['Expr'][ct.FlowKind.LazyValueFunc]
lazy_func = input_sockets['Expr'][ct.FlowKind.Func]
info = input_sockets['Expr'][ct.FlowKind.Info]
params = input_sockets['Expr'][ct.FlowKind.Params]
@ -422,9 +422,9 @@ class VizNode(base.MaxwellSimNode):
for sym in params.sorted_symbols
}
# Realize LazyValueFunc w/Symbolic Values, Unit System
# Realize Func w/Symbolic Values, Unit System
## -> This gives us the actual plot data!
data = lazy_value_func.func_jax(
data = lazy_func.func_jax(
*params.scaled_func_args(
unit_systems['BlenderUnits'], symbol_values=symbol_values
),

View File

@ -29,12 +29,12 @@ class ExprConstantNode(base.MaxwellSimNode):
input_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(
active_kind=ct.FlowKind.LazyValueFunc,
active_kind=ct.FlowKind.Func,
),
}
output_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(
active_kind=ct.FlowKind.LazyValueFunc,
active_kind=ct.FlowKind.Func,
show_info_columns=True,
),
}
@ -58,12 +58,12 @@ class ExprConstantNode(base.MaxwellSimNode):
@events.computes_output_socket(
# Trigger
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
# Loaded
input_sockets={'Expr'},
input_socket_kinds={'Expr': ct.FlowKind.LazyValueFunc},
input_socket_kinds={'Expr': ct.FlowKind.Func},
)
def compute_lazy_value_func(self, input_sockets: dict) -> typ.Any:
def compute_lazy_func(self, input_sockets: dict) -> typ.Any:
return input_sockets['Expr']
####################

View File

@ -42,7 +42,7 @@ class DataFileImporterNode(base.MaxwellSimNode):
'File Path': sockets.FilePathSocketDef(),
}
output_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
}
####################
@ -121,11 +121,11 @@ class DataFileImporterNode(base.MaxwellSimNode):
pass
####################
# - FlowKind.Array|LazyValueFunc
# - FlowKind.Array|Func
####################
@events.computes_output_socket(
'Expr',
kind=ct.FlowKind.LazyValueFunc,
kind=ct.FlowKind.Func,
input_sockets={'File Path'},
)
def compute_func(self, input_sockets: dict) -> td.Simulation:
@ -144,7 +144,7 @@ class DataFileImporterNode(base.MaxwellSimNode):
# Jax Compatibility: Lazy Data Loading
## -> Delay loading of data from file as long as we can.
if data_file_format.loader_is_jax_compatible:
return ct.LazyValueFuncFlow(
return ct.FuncFlow(
func=lambda: data_file_format.loader(file_path),
supports_jax=True,
)
@ -152,7 +152,7 @@ class DataFileImporterNode(base.MaxwellSimNode):
# No Jax Compatibility: Eager Data Loading
## -> Load the data now and bind it.
data = data_file_format.loader(file_path)
return ct.LazyValueFuncFlow(func=lambda: data, supports_jax=True)
return ct.FuncFlow(func=lambda: data, supports_jax=True)
return ct.FlowSignal.FlowPending
return ct.FlowSignal.FlowPending
@ -175,7 +175,7 @@ class DataFileImporterNode(base.MaxwellSimNode):
'Expr',
kind=ct.FlowKind.Info,
output_sockets={'Expr'},
output_socket_kinds={'Expr': ct.FlowKind.LazyValueFunc},
output_socket_kinds={'Expr': ct.FlowKind.Func},
)
def compute_info(self, output_sockets) -> ct.InfoFlow:
"""Declare an `InfoFlow` based on the data shape.

View File

@ -66,7 +66,7 @@ class DataFileExporterNode(base.MaxwellSimNode):
bl_label = 'Data File Importer'
input_sockets: typ.ClassVar = {
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.LazyValueFunc),
'Expr': sockets.ExprSocketDef(active_kind=ct.FlowKind.Func),
'File Path': sockets.FilePathSocketDef(),
}
@ -95,8 +95,8 @@ class DataFileExporterNode(base.MaxwellSimNode):
@property
def expr_data(self) -> typ.Any | None:
"""Retrieve the input expression's data by evaluating its `LazyValueFunc`."""
func = self._compute_input('Expr', kind=ct.FlowKind.LazyValueFunc)
"""Retrieve the input expression's data by evaluating its `Func`."""
func = self._compute_input('Expr', kind=ct.FlowKind.Func)
params = self._compute_input('Expr', kind=ct.FlowKind.Params)
has_func = not ct.FlowSignal.check(func)

View File

@ -133,7 +133,7 @@ class TemporalShapeNode(base.MaxwellSimNode):
},
input_socket_kinds={
't Range': ct.FlowKind.Range,
'Envelope': ct.FlowKind.LazyValueFunc,
'Envelope': ct.FlowKind.Func,
},
input_sockets_optional={
'max E': True,

View File

@ -525,9 +525,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
msg = f'Socket {self.bl_label} {self.socket_type}): Tried to set "ct.FlowKind.Array", but socket does not define it'
raise NotImplementedError(msg)
# LazyValueFunc
# Func
@property
def lazy_value_func(self) -> ct.LazyValueFuncFlow:
def lazy_func(self) -> ct.FuncFlow:
"""Throws a descriptive error.
Notes:
@ -538,8 +538,8 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
"""
return ct.FlowSignal.NoFlow
@lazy_value_func.setter
def lazy_value_func(self, lazy_value_func: ct.LazyValueFuncFlow) -> None:
@lazy_func.setter
def lazy_func(self, lazy_func: ct.FuncFlow) -> None:
"""Throws a descriptive error.
Notes:
@ -548,7 +548,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
Raises:
NotImplementedError: When used without being overridden.
"""
msg = f'Socket {self.bl_label} {self.socket_type}): Tried to set "ct.FlowKind.LazyValueFunc", but socket does not define it'
msg = f'Socket {self.bl_label} {self.socket_type}): Tried to set "ct.FlowKind.Func", but socket does not define it'
raise NotImplementedError(msg)
# Range
@ -595,7 +595,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
kind_data_map = {
ct.FlowKind.Value: lambda: self.value,
ct.FlowKind.Array: lambda: self.array,
ct.FlowKind.LazyValueFunc: lambda: self.lazy_value_func,
ct.FlowKind.Func: lambda: self.lazy_func,
ct.FlowKind.Range: lambda: self.lazy_range,
ct.FlowKind.Params: lambda: self.params,
ct.FlowKind.Info: lambda: self.info,
@ -784,7 +784,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
ct.FlowKind.Value: self.draw_value,
ct.FlowKind.Array: self.draw_array,
ct.FlowKind.Range: self.draw_lazy_range,
ct.FlowKind.LazyValueFunc: self.draw_lazy_value_func,
ct.FlowKind.Func: self.draw_lazy_func,
}[self.active_kind](col)
# Info Drawing
@ -914,11 +914,11 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
col: Target for defining UI elements.
"""
def draw_lazy_value_func(self, col: bpy.types.UILayout) -> None:
def draw_lazy_func(self, col: bpy.types.UILayout) -> None:
"""Draws the socket lazy value function UI on its own line.
Notes:
Should be overriden by individual socket classes, if they have an editable `FlowKind.LazyValueFunc`.
Should be overriden by individual socket classes, if they have an editable `FlowKind.Func`.
Parameters:
col: Target for defining UI elements.

View File

@ -100,7 +100,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
When active, `self.active_unit` can be used via the UI to select valid unit of the given `self.physical_type`, and `self.unit` works.
The enum itself can be dynamically altered, ex. via its UI dropdown support.
symbols: The symbolic variables valid in the context of the expression.
Various features, including `LazyValueFunc` support, become available when symbols are in use.
Various features, including `Func` support, become available when symbols are in use.
The presence of symbols forces fallback to a string-based `sympy` expression UI.
active_unit: The currently active unit, as a dropdown.
@ -544,20 +544,20 @@ class ExprBLSocket(base.MaxwellSimSocket):
]
####################
# - FlowKind: LazyValueFunc (w/Params if Constant)
# - FlowKind: Func (w/Params if Constant)
####################
@property
def lazy_value_func(self) -> ct.LazyValueFuncFlow:
def lazy_func(self) -> ct.FuncFlow:
"""Returns a lazy value that computes the expression returned by `self.value`.
If `self.value` has unknown symbols (as indicated by `self.symbols`), then these will be the arguments of the `LazyValueFuncFlow`.
If `self.value` has unknown symbols (as indicated by `self.symbols`), then these will be the arguments of the `FuncFlow`.
Otherwise, the returned lazy value function will be a simple excuse for `self.params` to pass the verbatim `self.value`.
"""
# Symbolic
## -> `self.value` is guaranteed to be an expression with unknowns.
## -> The function computes `self.value` with unknowns as arguments.
if self.symbols:
return ct.LazyValueFuncFlow(
return ct.FuncFlow(
func=sp.lambdify(
self.sorted_symbols,
spux.scale_to_unit(self.value, self.unit),
@ -572,7 +572,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
## -> ("Dummy" as in returns the same argument that it takes).
## -> This is an excuse to let `ParamsFlow` pass `self.value` verbatim.
## -> Generally only useful for operations with other expressions.
return ct.LazyValueFuncFlow(
return ct.FuncFlow(
func=lambda v: v,
func_args=[
self.physical_type if self.physical_type is not None else self.mathtype
@ -582,7 +582,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
@property
def params(self) -> ct.ParamsFlow:
"""Returns parameter symbols/values to accompany `self.lazy_value_func`.
"""Returns parameter symbols/values to accompany `self.lazy_func`.
If `self.value` has unknown symbols (as indicated by `self.symbols`), then these will be passed into `ParamsFlow`, which will thus be parameterized (and require realization before use).
Otherwise, `self.value` is passed verbatim as the only `ParamsFlow.func_arg`.
@ -605,13 +605,13 @@ class ExprBLSocket(base.MaxwellSimSocket):
@property
def info(self) -> ct.ArrayFlow:
r"""Returns parameter symbols/values to accompany `self.lazy_value_func`.
r"""Returns parameter symbols/values to accompany `self.lazy_func`.
The output name/size/mathtype/unit corresponds directly the `ExprSocket`.
If `self.symbols` has entries, then these will propagate as dimensions with unresolvable `RangeFlow` index descriptions.
The index range will be $(-\infty,\infty)$, with $0$ steps and no unit.
The order/naming matches `self.params` and `self.lazy_value_func`.
The order/naming matches `self.params` and `self.lazy_func`.
Otherwise, only the output name/size/mathtype/unit corresponding to the socket is passed along.
"""
@ -835,13 +835,13 @@ class ExprBLSocket(base.MaxwellSimSocket):
if self.steps != 0:
col.prop(self, self.blfields['steps'], text='')
def draw_lazy_value_func(self, col: bpy.types.UILayout) -> None:
def draw_lazy_func(self, col: bpy.types.UILayout) -> None:
"""Draw the socket body for a single flexible value/expression, for down-chain lazy evaluation.
This implements the most flexible variant of the `ExprSocket` UI, providing the user with full runtime-configuration of the exact `self.size`, `self.mathtype`, `self.physical_type`, and `self.symbols` of the expression.
Notes:
Drawn when `self.active_kind == FlowKind.LazyValueFunc`.
Drawn when `self.active_kind == FlowKind.Func`.
This is an ideal choice for ex. math nodes that need to accept arbitrary expressions as inputs, with an eye towards lazy evaluation of ex. symbolic terms.
@ -872,7 +872,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
# - UI: InfoFlow
####################
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
if self.active_kind == ct.FlowKind.LazyValueFunc and self.show_info_columns:
if self.active_kind == ct.FlowKind.Func and self.show_info_columns:
row = col.row()
box = row.box()
grid = box.grid_flow(
@ -927,7 +927,7 @@ class ExprSocketDef(base.SocketDef):
ct.FlowKind.Value,
ct.FlowKind.Range,
ct.FlowKind.Array,
ct.FlowKind.LazyValueFunc,
ct.FlowKind.Func,
] = ct.FlowKind.Value
# Socket Interface