Skip to content

Commit 0c1af14

Browse files
authored
Merge pull request #122 from scijava/scijava-function/extra-computers
Add additional Computers enabling Op outputs in different spots
2 parents 1404ed3 + cd6b36f commit 0c1af14

File tree

10 files changed

+2932
-119
lines changed

10 files changed

+2932
-119
lines changed

scijava-function/bin/generate.groovy

100644100755
File mode changed.

scijava-function/src/main/java/org/scijava/function/Computers.java

Lines changed: 2674 additions & 78 deletions
Large diffs are not rendered by default.

scijava-function/templates/main/java/org/scijava/function/Computers.list

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,27 @@ matchParams = ```
2121
String.join(', ', nilArgs)
2222
}
2323
```
24-
24+
permutedGenerics = ```
25+
{ arity, a ->
26+
generics = genericParamTypes(arity)
27+
outVal = generics.removeAt(arity)
28+
generics.add(a-1, outVal)
29+
'<' + String.join(', ', generics) + '>'
30+
}
31+
```
32+
permutedComputeParams = ```
33+
{ arity, a->
34+
typeParams = typeParamsList(arity)
35+
outVal = "@Container " + typeParams.removeAt(arity)
36+
typeParams.add(a-1, outVal)
37+
String.join(', ', typeParams)
38+
}
39+
```
40+
permutedComputeArgs = ```
41+
{ arity, a->
42+
names = genericsNamesList(arity)
43+
outVal = names.removeAt(arity)
44+
names.add(a-1, outVal)
45+
String.join(', ', names)
46+
}
47+
```

scijava-function/templates/main/java/org/scijava/function/Computers.vm

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,16 @@ public final class Computers {
3333
// NB: Prevent instantiation of utility class.
3434
}
3535

36-
// -- Static Utility Methods -- //
37-
3836
/**
39-
* All known computer types and their arities. The entries are sorted by
40-
* arity, i.e., the {@code i}-th entry has an arity of {@code i}. It might be
41-
* nice to use a BiMap from e.g. Google Guava, but that would require a
42-
* dependency on that component :(
37+
* All known computer types and their arities. The types are indexed, first by the number of <em>pure inputs</em>
38+
* in their functional method, then by the (1-indexed) <em>index</em> of the preallocated output.
39+
* <p>
40+
* Note that this data structure is populated at the bottom of the file, so it does not impede quick browsing
41+
* of the functional interfaces
42+
* </p>
4343
*/
44-
public static final HashMap<Integer, Class<?>> ALL_COMPUTERS;
45-
public static final HashMap<Class<?>, Integer> ALL_ARITIES;
46-
47-
static {
48-
ALL_COMPUTERS = new HashMap<>();
49-
ALL_ARITIES = new HashMap<>();
50-
#foreach($arity in $arities)
51-
ALL_COMPUTERS.put($arity, Computers.Arity${arity}.class);
52-
ALL_ARITIES.put(Computers.Arity${arity}.class, $arity);
53-
#end
54-
}
44+
public static final Class<?>[][] ALL_COMPUTERS = new Class<?>[$maxArity + 1][$maxArity + 1];
45+
public static final HashMap<Class<?>, Integer> ALL_ARITIES = new HashMap<>();
5546

5647
/**
5748
* @return {@code true} if the given type is a known
@@ -61,7 +52,7 @@ public final class Computers {
6152
* @throws NullPointerException If {@code c} is {@code null}.
6253
*/
6354
public static boolean isComputer(Class<?> c) {
64-
return ALL_COMPUTERS.containsValue(c);
55+
return ALL_ARITIES.containsKey(c);
6556
}
6657

6758
/**
@@ -72,8 +63,27 @@ public final class Computers {
7263
* arity {@code arity}.
7364
*/
7465
public static Class<?> computerOfArity(int arity) {
75-
if (ALL_COMPUTERS.containsKey(arity)) return ALL_COMPUTERS.get(arity);
76-
throw new IllegalArgumentException("No Computer of arity " + arity);
66+
// If pos is not given, we assume we're looking for one of the ArityX implementations,
67+
// which is theoretically equivalent to a ArityX_X.
68+
return computerOfArity(arity, arity);
69+
}
70+
71+
/**
72+
* @param arity an {@code int} corresponding to a {@code Computer} of that
73+
* arity.
74+
* @param pos an {@code int} corresponding to the position of the
75+
* {@code Computer}'s output
76+
* @return the {@code Computer} of arity {@code arity}.
77+
* @throws IllegalArgumentException iff there is no known {@code Computer} of
78+
* arity {@code arity}.
79+
*/
80+
public static Class<?> computerOfArity(int arity, int pos) {
81+
if ((arity > ALL_COMPUTERS.length + 1) || pos > arity) {
82+
throw new IllegalArgumentException( //
83+
"No Computer of arity " + arity + " with output index " + pos //
84+
);
85+
}
86+
return ALL_COMPUTERS[arity][pos];
7787
}
7888

7989
/**
@@ -104,4 +114,41 @@ public final class Computers {
104114
}
105115
}
106116
#end##foreach($arity)
117+
118+
#foreach($arity in [1..$maxArity])
119+
#foreach($a in [1..${arity}])
120+
#set($rawClass = "Arity${arity}_${a}")
121+
#set($genericParams = $permutedGenerics.call($arity, $a))
122+
#set($inheritedParams = $generics.call($arity))
123+
#set($gClass = "$rawClass$genericParams")
124+
125+
@FunctionalInterface
126+
public interface $gClass extends
127+
Arity$arity$inheritedParams
128+
{
129+
130+
void compute$a($permutedComputeParams.call($arity, $a));
131+
132+
@Override
133+
default void compute($computeParams.call($arity))
134+
{
135+
compute$a($permutedComputeArgs.call($arity, $a));
136+
}
137+
}
138+
#end##foreach($a)
139+
#end##foreach($arity)
140+
141+
static {
142+
#foreach($arity in $arities)
143+
#end
144+
#foreach($arity in [1..$maxArity])
145+
#foreach($a in [1..$arity])
146+
#set($pos = $a - 1)
147+
ALL_COMPUTERS[$arity][$pos] = Computers.Arity${arity}_${a}.class;
148+
ALL_ARITIES.put(Computers.Arity${arity}_${a}.class, $arity);
149+
#end
150+
ALL_COMPUTERS[$arity][$arity] = Computers.Arity${arity}.class;
151+
ALL_ARITIES.put(Computers.Arity${arity}.class, $arity);
152+
#end
153+
}
107154
}

scijava-function/templates/main/java/org/scijava/function/Inplaces.vm

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ package org.scijava.function;
88
import java.util.Collections;
99
import java.util.LinkedHashMap;
1010
import java.util.List;
11-
import java.util.Map;
1211
import java.util.HashMap;
12+
import java.util.Map;
1313
import java.util.Map.Entry;
14+
import java.util.Objects;
1415
import java.util.function.BiConsumer;
1516
import java.util.function.Consumer;
1617
import java.util.stream.Collectors;
@@ -167,5 +168,17 @@ public final class Inplaces {
167168
public int mutablePosition() {
168169
return mutablePosition;
169170
}
171+
172+
@Override
173+
public boolean equals(Object that) {
174+
if (!(that instanceof InplaceInfo)) return false;
175+
InplaceInfo other = (InplaceInfo) that;
176+
return other.arity == arity && other.mutablePosition == mutablePosition;
177+
}
178+
179+
@Override
180+
public int hashCode() {
181+
return Objects.hash(arity, mutablePosition);
182+
}
170183
}
171184
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaMethodYAMLInfoCreator.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.lang.reflect.Parameter;
3434
import java.net.URI;
3535
import java.util.Map;
36-
import java.util.regex.Pattern;
3736

3837
import org.scijava.common3.Classes;
3938
import org.scijava.function.Computers;
@@ -107,25 +106,39 @@ private Class<?> deriveOpType(String identifier, String typeString,
107106
" could not be loaded: Computers and Inplaces must declare their Op type in their @implNote annotation For example, if your Inplace is designed to mutate the first argument, please write \"type='Inplace1'\"");
108107
}
109108
}
110-
// Handle op type inference
111-
if (Pattern.matches("^[Ii]nplace\\s*[0-9]*$", typeString)) {
112-
try {
113-
int ioIndex = Integer.parseInt(typeString.replaceAll("[^0-9]", "")) - 1;
114-
return Inplaces.inplaceOfArity(parameterCount, ioIndex);
115-
}
116-
catch (NumberFormatException e) {
117-
throw new RuntimeException("Op " + identifier +
118-
" could not be loaded: Inplaces must declare the index of the mutable parameter. For example, if your Inplace is designed to mutate the first argument, please write \"Inplace1\"");
109+
110+
Class<?> alias = findAlias(typeString.trim(), parameterCount);
111+
if (alias != null) return alias;
112+
113+
// Finally, pass off to the class loader function.
114+
return deriveType(identifier, typeString);
115+
}
116+
117+
private Class<?> findAlias(String typeString, int parameterCount) {
118+
// We match any aliases matching the regex pattern "(or_of_aliases)(\\d*)"
119+
int ioPosition = parameterCount - 1;
120+
int aliasEnd = typeString.length() - 1;
121+
// If the last char is a digit, we have a positional suffix
122+
if (Character.isDigit(typeString.charAt(aliasEnd))) {
123+
// Find the positional suffix
124+
for (; aliasEnd >= 0; aliasEnd--) {
125+
if (!Character.isDigit(typeString.charAt(aliasEnd))) {
126+
ioPosition = Integer.parseInt(typeString.substring(aliasEnd + 1)) - 1;
127+
break;
128+
}
119129
}
120130
}
121-
else if (Pattern.matches("^[Cc]omputer\\s*[0-9]*$", typeString)) {
122-
return Computers.computerOfArity(parameterCount - 1);
123-
}
124-
else if (Pattern.matches("^[Ff]unction\\s*[0-9]*$", typeString)) {
125-
return Functions.functionOfArity(parameterCount);
131+
// Check if (typeString - positional suffix) is an alias
132+
switch (typeString.substring(0, aliasEnd + 1)) {
133+
case "Computer":
134+
return Computers.computerOfArity(parameterCount - 1, ioPosition);
135+
case "Function":
136+
return Functions.functionOfArity(parameterCount);
137+
case "Inplace":
138+
return Inplaces.inplaceOfArity(parameterCount, ioPosition);
139+
default:
140+
return null;
126141
}
127-
// Finally, pass off to the class loader function.
128-
return deriveType(identifier, typeString);
129142
}
130143

131144
private Class<?> deriveType(String identifier, String typeString) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
package org.scijava.ops.engine.adapt.functional;
3+
4+
import java.util.function.BiFunction;
5+
6+
import org.junit.jupiter.api.Assertions;
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.Test;
9+
import org.scijava.function.Computers;
10+
import org.scijava.function.Container;
11+
import org.scijava.ops.engine.AbstractTestEnvironment;
12+
import org.scijava.ops.engine.create.CreateOpCollection;
13+
import org.scijava.ops.spi.OpCollection;
14+
import org.scijava.ops.spi.OpMethod;
15+
16+
/**
17+
* Tests that a permuted computer, such as {@link Computers.Arity2_1}, can be
18+
* adapted as a {@link BiFunction}.
19+
*
20+
* @author Gabriel Selzer
21+
*/
22+
public class PermutedComputerToFunctionAdaptTest extends AbstractTestEnvironment
23+
implements OpCollection
24+
{
25+
26+
@BeforeAll
27+
public static void addNeededOps() {
28+
ops.register(new PermutedComputerToFunctionAdaptTest());
29+
ops.register(
30+
new ComputersToFunctionsViaFunction.Computer2ToFunction2ViaFunction<>());
31+
ops.register(new CreateOpCollection());
32+
}
33+
34+
@OpMethod(names = "adapt.permuted", type = Computers.Arity2_1.class)
35+
public static void compute(@Container double[] out, double[] in1,
36+
double[] in2)
37+
{
38+
for (int i = 0; i < in1.length; i++) {
39+
out[i] = in1[i] * in2[i];
40+
}
41+
}
42+
43+
@Test
44+
public void testAdaptingPermutedComputer() {
45+
double[] result = ops.binary("adapt.permuted").input(new double[] { 1.5 },
46+
new double[] { 2.0 }).outType(double[].class).apply();
47+
Assertions.assertEquals(3.0, result[0]);
48+
}
49+
50+
}

scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NullableArgumentsTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.junit.jupiter.api.BeforeAll;
3434
import org.junit.jupiter.api.Test;
3535
import org.scijava.function.Computers;
36+
import org.scijava.function.Container;
3637
import org.scijava.function.Functions;
3738
import org.scijava.ops.engine.AbstractTestEnvironment;
3839
import org.scijava.ops.spi.OpCollection;
@@ -163,4 +164,48 @@ public void testMethodWithoutNullables() {
163164
Assertions.assertEquals(expected, out);
164165
}
165166

167+
@OpMethod(names = "test.nullableOr", type = Computers.Arity3_3.class)
168+
public static void nullablePermutedComputer( //
169+
int[] in1, //
170+
@Nullable int[] in2, //
171+
@Container int[] out, //
172+
@Nullable int[] in3 //
173+
) {
174+
if (in2 == null) in2 = new int[in1.length];
175+
if (in3 == null) in3 = new int[in1.length];
176+
for (int i = 0; i < out.length; i++) {
177+
out[i] = in1[i] | in2[i] | in3[i];
178+
}
179+
}
180+
181+
@Test
182+
public void testPermutedMethodWithTwoNullables() {
183+
int[] out = new int[1];
184+
ops.op("test.nullableOr").arity3().input( //
185+
new int[] { 1 }, //
186+
new int[] { 2 }, //
187+
new int[] { 4 } //
188+
).output(out).compute();
189+
Assertions.assertEquals(7, out[0]);
190+
}
191+
192+
@Test
193+
public void testPermutedMethodWithOneNullable() {
194+
int[] out = new int[1];
195+
ops.op("test.nullableOr").arity2().input( //
196+
new int[] { 1 }, //
197+
new int[] { 2 } //
198+
).output(out).compute();
199+
Assertions.assertEquals(3, out[0]);
200+
}
201+
202+
@Test
203+
public void testPermutedMethodWithoutNullables() {
204+
int[] out = new int[1];
205+
ops.op("test.nullableOr").arity1().input( //
206+
new int[] { 1 } //
207+
).output(out).compute();
208+
Assertions.assertEquals(1, out[0]);
209+
}
210+
166211
}

scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/YAMLOpTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,16 @@ public void testYAMLMethodComputerShortType() {
8989
List<Integer> l2 = Arrays.asList(3);
9090
List<Integer> out = new ArrayList<>();
9191
ops.op("example.and").arity2().input(l1, l2).output(out).compute();
92-
Assertions.assertEquals(1, l1.get(0), 1e-6);
92+
Assertions.assertEquals(1, out.get(0), 1e-6);
93+
}
94+
95+
@Test
96+
public void testYAMLMethodExtraComputerShortType() {
97+
List<Integer> l1 = Arrays.asList(1);
98+
List<Integer> l2 = Arrays.asList(2);
99+
List<Integer> out = new ArrayList<>();
100+
ops.op("example.or").arity2().input(l1, l2).output(out).compute();
101+
Assertions.assertEquals(3, out.get(0), 1e-6);
93102
}
94103

95104
@Test

scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLMethodOp.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class YAMLMethodOp {
4242
/**
4343
* An example Op, implemented by a {@link Method}
4444
*
45-
* @implNote op name=example.sub
45+
* @implNote op name=example.sub, type=Function
4646
* @param aDouble the first double
4747
* @param aDouble2 the second double
4848
* @return the difference
@@ -82,4 +82,21 @@ public static void and(List<Integer> aList, List<Integer> aList2,
8282
}
8383
}
8484

85+
/**
86+
* A third example Op, implemented by a {@link Method}
87+
*
88+
* @implNote op name=example.or, type=Computer2
89+
* @param aList the first integer {@link List}
90+
* @param out the logical and of the two integer {@link List}s
91+
* @param aList2 the second integer {@link List}
92+
*/
93+
public static void or(List<Integer> aList, List<Integer> out,
94+
List<Integer> aList2)
95+
{
96+
out.clear();
97+
for (int i = 0; i < aList.size(); i++) {
98+
out.add(aList.get(i) | aList2.get(i));
99+
}
100+
}
101+
85102
}

0 commit comments

Comments
 (0)