Skip to content

Commit 854288f

Browse files
authored
Create simplification framework (#25)
* Create "converted" struct based on simplifiers * Lean on matcher to find simplifed matches The DefaultOpMatcher already has methods to return a single match from a list of OpRefs, which means that if we change the return of simplifyOp to a list of OpRefs, we can make use of this code. * Isolate SimplifyTest Now the tests depend on an Op within the test itself, which prevents changes to external Ops from affecting the outcome of the test * Convert Simplifiers to Plugins This allows us to extensibly discover simplifiers * Add simplifiers for all boxed primitive Numbers * OpRef: only one op type allowed OpRefs with more types are: * More complicated * Never used in practice These things (not sure what they will be yet) calculate the magnitude of the (worst-case) loss when converting between two types * Clean DefaultOpEnvironment.simplifyInfo * convert all conversionLoss behavior to ops These Ops are now called LossReporters * Improve Javassist simplified Op creation This commit does two things. Firstly, it provides a proof of concept for generic simplifiers. Secondly, it adds the logic necessary to simplify to/from types that are not directly related to each other (as illustrated in the test, where the simplification chain is Byte[] -> ObjectArray<Number> -> Integer[]) * mature optimization The startup time was greatly increased due to the simplification process. We can mitigate a lot of this time by lazily initializing elements of the SimplifiedOpInfos, such as their simplified type and their simplified Struct. * Only create simplified infos when necessary We now create the simplifiedOpInfos for Ops whose name matches the Op request. This lazy approach saves time and opens the possibility for us to refactor Simplifiers into Ops * WIP: Use Ops for simplification/focusing * Remove optional parameter String in OpUtils Optional parameters are no longer allowed * Assume infinite loss when no lossReporters exist * Test direct match to simplified OpInfo * Improve retyping for edge case scenarios In rare edge cases, functional interfaces don't declare their abstract method. This might happen for a convenience interface like: interface AllSameFunction<T> extends BiFunction<T, T, T> {} such an interface does not have a declared abstract method, so we have to find it via its superinterface BiFunction. The method we get out of BiFunction, however, will not know of the mapping of the Ts in AllSameFunction to the T, U, R of BiFunction; this is a bug in the method. This commit adds logic that determines the mappings of the type variables of interfaces like AllSameFunction to the type variables of BiFunction, so that Ops implementing AllSameFunction can be retyped. * Remove obsolete data structure This data strucutre held the Simplifiers, which we no longer use (since we converted to an Ops-based simplification framework) * Clean DefaultOpEnvironment * Purge commented code * Expose problem with non-reifiable lambdas * Add simplification to mutate input arguments * Clean code * Extract simplified Op creation to utility class * Remove unused import * Add synthetic check to reification * Add example tests for inplace simplification * Take the work out of SimplifiedOpCandidate SimplifiedOpCandidate was taking on too much work. I would rather this work be extracted to a helper class, so that the SimplifiedOpInfo can be called without needing the SimplifiedOpCandidate. Of course, SimplifiedOpCandidate can't go away, since some functionality (like determining loss) is probably better performed there. * Test simplification priority * Improve determination of suitable Op wrapper When Op wrappers exist for both a class and one of its superclasses, Op wrapping would fail as multiple wrappers would be suitable. The Op wrapping logic was not smart enough to filter these matches in any way. This commit adds a method to filter superclasses out of the list of suitable wrappers. Since these wrappers also preserve the exact supertype of the Op w.r.t. the wrapper class, it is desirable to have subclasses declare their own wrappers, and this fix prevents multiple suitable wrappers from preventing a wrapper match (so long as all classes are related). This commit also fixes a bug in the error message when multiple wrappers would match. The error was designed to print the classes of each wrapper that matched, but would instead vomit out the classes of all known wrappers. This bug has also been fixed.
1 parent b57a28f commit 854288f

46 files changed

Lines changed: 2955 additions & 112 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scijava/scijava-ops/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
exports org.scijava.ops.core; // contains OpCollection, Op interfaces
77
exports org.scijava.ops.core.builder; // contains OpBuilder classes
88
exports org.scijava.ops.matcher;
9+
exports org.scijava.ops.simplify;
10+
exports org.scijava.ops.conversionLoss;
911
// TODO: move OpWrapper to its own package (org.scijava.ops.wrap??)
1012
exports org.scijava.ops.util; // contains OpWrapper interface
1113
exports org.scijava.struct;

scijava/scijava-ops/src/main/java/org/scijava/ops/OpEnvironment.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public interface OpEnvironment {
6868

6969
<T> T op(final String opName, final Nil<T> specialType, final Nil<?>[] inTypes, final Nil<?> outType);
7070

71+
<T> T op(OpInfo info, Nil<T> specialType, Nil<?>[] inTypes, Nil<?> outType);
72+
7173
default OpBuilder op(final String opName) {
7274
return new OpBuilder(this, opName);
7375
}

scijava/scijava-ops/src/main/java/org/scijava/ops/OpInfo.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33

44
import java.lang.reflect.AnnotatedElement;
55
import java.lang.reflect.Type;
6+
import java.lang.reflect.TypeVariable;
67
import java.util.List;
8+
import java.util.Map;
79

10+
import org.scijava.log.Logger;
11+
import org.scijava.ops.matcher.OpCandidate;
12+
import org.scijava.ops.matcher.OpRef;
813
import org.scijava.param.ValidityException;
914
import org.scijava.struct.Member;
1015
import org.scijava.struct.Struct;
@@ -24,6 +29,9 @@ public interface OpInfo extends Comparable<OpInfo> {
2429

2530
/** Gets the associated {@link Struct} metadata. */
2631
Struct struct();
32+
33+
/** Describes whether this Op can be simplified. */
34+
boolean isSimplifiable();
2735

2836
/** Gets the op's input parameters. */
2937
default List<Member<?>> inputs() {
@@ -39,6 +47,10 @@ default Member<?> output() {
3947
default List<OpDependencyMember<?>> dependencies() {
4048
return OpUtils.dependencies(struct());
4149
}
50+
51+
default OpCandidate createCandidate(OpEnvironment env, Logger log, OpRef ref, Map<TypeVariable<?>, Type> typeVarAssigns) {
52+
return new OpCandidate(env, log, ref, this, typeVarAssigns);
53+
}
4254

4355
/** The op's priority. */
4456
double priority();

scijava/scijava-ops/src/main/java/org/scijava/ops/OpUtils.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ public static Type[] inputTypes(OpCandidate candidate) {
132132
return getTypes(inputs(candidate.struct()));
133133
}
134134

135+
public static Type[] inputTypes(Struct struct) {
136+
return getTypes(inputs(struct));
137+
}
138+
135139
public static Member<?> output(OpCandidate candidate) {
136140
return candidate.opInfo().output();
137141
}
@@ -174,7 +178,7 @@ public static Type[] types(OpCandidate candidate) {
174178
}
175179

176180
public static double getPriority(final OpCandidate candidate) {
177-
return candidate.opInfo().priority();
181+
return candidate.priority();
178182
}
179183

180184
public static Type[] padTypes(final OpCandidate candidate, Type[] types) {
@@ -397,8 +401,6 @@ private static String paramString(final Iterable<Member<?>> items, final Member<
397401

398402
if (!typeOnly) {
399403
sb.append(" " + item.getKey());
400-
if (!((ParameterMember<?>) item).isRequired())
401-
sb.append("?");
402404
}
403405
}
404406
return sb.toString();
@@ -426,11 +428,10 @@ public static String opString(final OpInfo info) {
426428
}
427429

428430
public static Class<?> findFirstImplementedFunctionalInterface(final OpRef opRef) {
429-
for (final Type opType : opRef.getTypes()) {
430-
final Class<?> functionalInterface = ParameterStructs.findFunctionalInterface(Types.raw(opType));
431-
if (functionalInterface != null) {
432-
return functionalInterface;
433-
}
431+
final Class<?> functionalInterface = ParameterStructs
432+
.findFunctionalInterface(Types.raw(opRef.getType()));
433+
if (functionalInterface != null) {
434+
return functionalInterface;
434435
}
435436
return null;
436437
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
package org.scijava.ops.conversionLoss;
3+
4+
import org.scijava.ops.core.Op;
5+
import org.scijava.ops.simplify.Unsimplifiable;
6+
import org.scijava.param.Parameter;
7+
import org.scijava.plugin.Plugin;
8+
import org.scijava.struct.ItemIO;
9+
import org.scijava.types.Nil;
10+
11+
/**
12+
* A {@link LossReporter} used when a type is not simplified.
13+
*
14+
* @author Gabriel Selzer
15+
* @param <T> - the type that is not being simplified.
16+
*/
17+
@Unsimplifiable
18+
@Plugin(type = Op.class, name = "lossReporter")
19+
@Parameter(key = "fromType")
20+
@Parameter(key = "toType")
21+
@Parameter(key = "maximumLoss", itemIO = ItemIO.OUTPUT)
22+
public class IdentityLossReporter<T> implements LossReporter<T, T> {
23+
24+
@Override
25+
public Double apply(Nil<T> t, Nil<T> u) {
26+
return 0.;
27+
}
28+
29+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
package org.scijava.ops.conversionLoss;
3+
4+
import java.lang.reflect.Type;
5+
import java.util.function.BiFunction;
6+
7+
import org.scijava.types.Nil;
8+
9+
/**
10+
* {@link BiFunction} reporting the <b>worst-case loss</b> of a conversion
11+
* from a {@link Type} from a {@link Type} {@code t} to a {@code Type r}.
12+
*
13+
* @author Gabriel Selzer
14+
* @param <T> - the {@code Type} that we are converting <b>from</b>
15+
* @param <R> - the {@code Type} that we are converting <b>to</b>
16+
*/
17+
@FunctionalInterface
18+
public interface LossReporter<T, R> extends
19+
BiFunction<Nil<T>, Nil<R>, Double>
20+
{
21+
@Override
22+
Double apply(Nil<T> from, Nil<R> to);
23+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.scijava.ops.conversionLoss;
2+
3+
import java.lang.reflect.Type;
4+
5+
import org.scijava.ops.util.OpWrapper;
6+
import org.scijava.plugin.Plugin;
7+
import org.scijava.types.GenericTyped;
8+
import org.scijava.types.Nil;
9+
10+
/**
11+
* An {@link OpWrapper} for {@link LossReporter}s.
12+
* TODO: would be nice if this was unnecessary.
13+
*
14+
* @author Gabriel Selzer
15+
*
16+
* @param <I>
17+
* @param <O>
18+
*/
19+
@Plugin(type = OpWrapper.class)
20+
public class LossReporterWrapper<I, O> //
21+
implements //
22+
OpWrapper<LossReporter<I, O>>
23+
{
24+
25+
@Override
26+
public LossReporter<I, O> wrap( //
27+
final LossReporter<I, O> op, //
28+
final Type reifiedType)
29+
{
30+
class GenericTypedLossReporter implements //
31+
LossReporter<I, O>, //
32+
GenericTyped //
33+
{
34+
35+
@Override
36+
public Double apply(Nil<I> from, Nil<O> to) //
37+
{
38+
return op.apply(from, to);
39+
}
40+
41+
@Override
42+
public Type getType() {
43+
return reifiedType;
44+
}
45+
}
46+
return new GenericTypedLossReporter();
47+
}
48+
}
49+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
package org.scijava.ops.conversionLoss;
3+
4+
import org.scijava.types.Nil;
5+
6+
/**
7+
* A subtype of {@link LossReporter} that reports 0 loss. This makes declaring
8+
* lossless {@code LossReporter}s cleaner.
9+
*
10+
* @author G
11+
* @param <T>
12+
* @param <R>
13+
*/
14+
public interface LosslessReporter<T, R> extends LossReporter<T, R> {
15+
16+
@Override
17+
default Double apply(Nil<T> from, Nil<R> to) {
18+
return 0.;
19+
}
20+
21+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.scijava.ops.conversionLoss;
2+
3+
import org.scijava.ops.OpField;
4+
import org.scijava.ops.core.OpCollection;
5+
import org.scijava.ops.simplify.Unsimplifiable;
6+
import org.scijava.plugin.Plugin;
7+
8+
@Plugin(type = OpCollection.class)
9+
public class PrimitiveArrayLossReporters {
10+
11+
// @Unsimplifiable
12+
// @Plugin(type = Op.class)
13+
// static class ByteArrayIntArrayReporter implements LosslessReporter<Byte[], Integer[]> {}
14+
15+
@Unsimplifiable
16+
@OpField(names = "lossReporter")
17+
public final LossReporter<Byte[], Integer[]> bArrIArr = (from, to) -> 0.;
18+
19+
@Unsimplifiable
20+
@OpField(names = "lossReporter")
21+
public final LossReporter<Double[], Integer[]> dArrIArr = (from, to) -> 0.;
22+
23+
}
24+
25+
//@Plugin(type = Op.class, name = "lossReporter")
26+
//@Parameter(key = "fromNil")
27+
//@Parameter(key = "toNil")
28+
//@Parameter(key = "loss", itemIO = ItemIO.OUTPUT)
29+
//public static class ArrayLossReporter<T extends Number, U extends Number> implements LossReporter<T[], U[]>{
30+
//
31+
// @OpDependency(name = "lossReporter")
32+
// private LossReporter<T, U> elementReporter;
33+
//
34+
// @Override
35+
// public Double apply(Nil<T[]> from, Nil<U[]> to) {
36+
// Nil<T> fromElement = Nil.of(from.getType())
37+
// }
38+
//
39+
//}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
package org.scijava.ops.conversionLoss;
3+
4+
import org.scijava.ops.OpField;
5+
import org.scijava.ops.core.OpCollection;
6+
import org.scijava.ops.simplify.Unsimplifiable;
7+
import org.scijava.plugin.Plugin;
8+
import org.scijava.types.Nil;
9+
10+
/**
11+
* An {@link OpCollection} containing the {@link LossReporter}s for primitive
12+
* type conversions
13+
*
14+
* @author Gabriel Selzer
15+
*/
16+
@Plugin(type = OpCollection.class)
17+
public class PrimitiveLossReporters {
18+
19+
@FunctionalInterface
20+
static interface IntToDecimalReporter<T, R> extends LossReporter<T, R> {
21+
22+
Double intToDecimalLoss(Nil<T> from, Nil<R> to);
23+
24+
@Override
25+
default Double apply(Nil<T> from, Nil<R> to) {
26+
return 1 + intToDecimalLoss(from, to);
27+
}
28+
29+
}
30+
31+
32+
@Unsimplifiable
33+
@OpField(names = "lossReporter")
34+
public final IntToDecimalReporter<Long, Double> LongDoubleReporter = (from, to) -> {
35+
long maxValue = Long.MAX_VALUE - 1;
36+
double converted = maxValue;
37+
return (double) Math.abs(maxValue - (long) converted);
38+
};
39+
40+
@Unsimplifiable
41+
@OpField(names = "lossReporter")
42+
public final LossReporter<Double, Long> DoubleLongReporter = (from, to) -> {
43+
double maxValue = Double.MAX_VALUE;
44+
long converted = (long) maxValue;
45+
return maxValue - converted;
46+
};
47+
48+
@Unsimplifiable
49+
@OpField(names = "lossReporter")
50+
public final LossReporter<Long, Integer> LongIntegerReporter = (from, to) -> {
51+
long maxValue = Long.MAX_VALUE;
52+
int converted = (int) maxValue;
53+
return (double) Math.abs(maxValue - converted);
54+
};
55+
56+
@Unsimplifiable
57+
@OpField(names = "lossReporter")
58+
public final LossReporter<Integer, Long> IntegerLongReporter = (from, to) -> {
59+
return 0.;
60+
};
61+
62+
@Unsimplifiable
63+
@OpField(names = "lossReporter")
64+
public final IntToDecimalReporter<Integer, Double> IntegerDoubleReporter = (from, to) -> {
65+
long maxValue = Integer.MAX_VALUE;
66+
double converted = maxValue;
67+
return (double) Math.abs(maxValue - (long) converted);
68+
};
69+
70+
@Unsimplifiable
71+
@OpField(names = "lossReporter")
72+
public final LossReporter<Double, Integer> DoubleIntegerReporter = (from, to) -> {
73+
double maxValue = Double.MAX_VALUE;
74+
int converted = (int) maxValue;
75+
return maxValue - converted;
76+
};
77+
78+
@Unsimplifiable
79+
@OpField(names = "lossReporter")
80+
public final LossReporter<Number, Double> NumberDoubleReporter = (from,
81+
to) -> LongDoubleReporter.apply(Nil.of(Long.class), Nil.of(Double.class));
82+
83+
@Unsimplifiable
84+
@OpField(names = "lossReporter")
85+
public final LossReporter<Number, Long> NumberLongReporter = (from,
86+
to) -> DoubleLongReporter.apply(Nil.of(Double.class), Nil.of(Long.class));
87+
}

0 commit comments

Comments
 (0)