E-TE0026 — Dependent result type in a collection operation
Error Type |
|
|---|---|
Phase |
type checking the model |
Python exception |
|
Message
A function of type `{function_type}` produces output elements whose type depends on its argument, and JijModeling cannot give them one common type.
Operations that build a collection from a function, such as `map`, `flat_map`, `genarray`, and `gendict`, need one type for all output elements (for `flat_map`, the elements of the returned collections). Where part of that type depends on the argument, JijModeling replaces it with a broader type that does not. For example, the type of numbers below the argument, or below a value computed from it, becomes the type of all natural numbers. JijModeling does not do this for array lengths or function parameter types.
Possible fix: make the array lengths and function parameter types in each output element's type independent of the argument. For an array whose length depends on the argument, build a stream instead, for example `jm.map(f, n)` instead of `jm.genarray(f, n)`, because the type of a stream has no length.
Cause
jm.map, jm.genarray, and jm.gendict require a common type for the values returned by their function.
jm.flat_map requires each returned value to be iterable and requires a common type for the elements it contributes to the output.
When an output element’s type depends on the function’s argument, the checker looks for a common type by relaxing the dependent parts:
the type of numbers below a value that depends on the argument becomes the type of all natural numbers;
a dictionary key type that depends on the argument is relaxed by the same rules, and a total dictionary with such a key becomes a partial dictionary, because a total dictionary has exactly the keys of its key type. For example, with
S = problem.Natural("S", ndim=1),jm.map(lambda n: jm.gendict(lambda i: 1.0, n), S)is accepted, and its elements have typePartialDict[natural; float].
The checker does not relax an array length or a function parameter type that depends on the argument.
For example, jm.map(lambda n: jm.genarray(lambda i: 1.0 + i, n), S) returns an array of length n for each n in S.
These lengths can differ between the elements of S, and an array of unspecified length is never used as their common type, so an array length that mentions the argument always causes this error, even where the lengths happen to be equal.
Similarly, a function of type (n : natural) -> n -> bool returns, for each n, a function that accepts only numbers below n.
In printed types, a bare expression where a type is expected, such as the inner n, denotes the natural numbers below that value.
For function parameter types, a common type can exist, but the checker does not look for one, so this error means that none was found, not that none exists.
Fix
Make the array lengths and function parameter types in each output element’s type independent of the function’s argument.
For an array whose length depends on the argument, return a stream instead. For example,
jm.map(lambda n: jm.map(lambda i: 1.0 + i, n), S)returns a stream of floats for eachn, and the type of a stream has no length. A stream can be iterated, filtered, and summed, but not indexed.To keep indexable arrays, give every returned array one common length that does not depend on the argument, such as
S.max(), and skip the unused entries with a condition where the arrays are used. The function must then be valid at every index below the common length, including the skipped ones.To collect all entries in one collection, use
jm.flat_map, for examplejm.flat_map(lambda n: jm.map(lambda i: (n, i), n), S), which produces the(n, i)pairs as one stream.If the rows of different lengths are instance data, declare them as a jagged-array placeholder (
jagged=True) instead of building them with a function.For returned functions, accept a common range of arguments, for example every natural, and enforce the bound where both numbers are available.