Skip to content
This repository was archived by the owner on Jul 17, 2024. It is now read-only.

Commit 8ffe5b2

Browse files
feat: add support for Decimal and Decimal score types
- Decimal maps mostly to BigDecimal, although its floating point concepts are ignored (Python does not have an infinite precision MathContext, so it acts more like a dynamic range floating point with an adjustable precision. The precision used is shared in a thread local object that can be changed using decimal.setcontext. - Added `str` constructors to `float` and `int` - Added sanity tests for all variants of penalize/reward/impact and score types
1 parent e1dac95 commit 8ffe5b2

31 files changed

+4090
-90
lines changed

jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ public class PythonClassTranslator {
6666
// $ is illegal in variables/methods in Python
6767
public static final String TYPE_FIELD_NAME = "$TYPE";
6868
public static final String CPYTHON_TYPE_FIELD_NAME = "$CPYTHON_TYPE";
69-
private static final String JAVA_METHOD_PREFIX = "$method$";
70-
private static final String PYTHON_JAVA_TYPE_MAPPING_PREFIX = "$pythonJavaTypeMapping";
69+
public static final String JAVA_METHOD_PREFIX = "$method$";
70+
public static final String PYTHON_JAVA_TYPE_MAPPING_PREFIX = "$pythonJavaTypeMapping";
7171

7272
public record PreparedClassInfo(PythonLikeType type, String className, String classInternalName) {
7373
}

jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ai.timefold.jpyinterpreter.implementors;
22

33
import java.lang.reflect.Field;
4+
import java.math.BigDecimal;
45
import java.math.BigInteger;
56
import java.util.IdentityHashMap;
67
import java.util.Iterator;
@@ -31,6 +32,7 @@
3132
import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple;
3233
import ai.timefold.jpyinterpreter.types.errors.TypeError;
3334
import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean;
35+
import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal;
3436
import ai.timefold.jpyinterpreter.types.numeric.PythonFloat;
3537
import ai.timefold.jpyinterpreter.types.numeric.PythonInteger;
3638
import ai.timefold.jpyinterpreter.types.numeric.PythonNumber;
@@ -65,76 +67,79 @@ public static PythonLikeObject wrapJavaObject(Object object, Map<Object, PythonL
6567
return existingObject;
6668
}
6769

68-
if (object instanceof OpaqueJavaReference) {
69-
return ((OpaqueJavaReference) object).proxy();
70+
if (object instanceof OpaqueJavaReference opaqueJavaReference) {
71+
return opaqueJavaReference.proxy();
7072
}
7173

72-
if (object instanceof PythonLikeObject) {
74+
if (object instanceof PythonLikeObject instance) {
7375
// Object already a PythonLikeObject; need to do nothing
74-
return (PythonLikeObject) object;
76+
return instance;
7577
}
7678

7779
if (object instanceof Byte || object instanceof Short || object instanceof Integer || object instanceof Long) {
7880
return PythonInteger.valueOf(((Number) object).longValue());
7981
}
8082

81-
if (object instanceof BigInteger) {
82-
return PythonInteger.valueOf((BigInteger) object);
83+
if (object instanceof BigInteger integer) {
84+
return PythonInteger.valueOf(integer);
85+
}
86+
87+
if (object instanceof BigDecimal decimal) {
88+
return new PythonDecimal(decimal);
8389
}
8490

8591
if (object instanceof Float || object instanceof Double) {
8692
return PythonFloat.valueOf(((Number) object).doubleValue());
8793
}
8894

89-
if (object instanceof Boolean) {
90-
return PythonBoolean.valueOf((Boolean) object);
95+
if (object instanceof Boolean booleanValue) {
96+
return PythonBoolean.valueOf(booleanValue);
9197
}
9298

93-
if (object instanceof String) {
94-
return PythonString.valueOf((String) object);
99+
if (object instanceof String string) {
100+
return PythonString.valueOf(string);
95101
}
96102

97-
if (object instanceof Iterator) {
98-
return new DelegatePythonIterator<>((Iterator) object);
103+
if (object instanceof Iterator iterator) {
104+
return new DelegatePythonIterator<>(iterator);
99105
}
100106

101-
if (object instanceof List) {
107+
if (object instanceof List list) {
102108
PythonLikeList out = new PythonLikeList();
103109
createdObjectMap.put(object, out);
104-
for (Object item : (List) object) {
110+
for (Object item : list) {
105111
out.add(wrapJavaObject(item));
106112
}
107113
return out;
108114
}
109115

110-
if (object instanceof Set) {
116+
if (object instanceof Set set) {
111117
PythonLikeSet out = new PythonLikeSet();
112118
createdObjectMap.put(object, out);
113-
for (Object item : (Set) object) {
119+
for (Object item : set) {
114120
out.add(wrapJavaObject(item));
115121
}
116122
return out;
117123
}
118124

119-
if (object instanceof Map) {
125+
if (object instanceof Map map) {
120126
PythonLikeDict out = new PythonLikeDict();
121127
createdObjectMap.put(object, out);
122-
Set<Map.Entry<?, ?>> entrySet = ((Map) object).entrySet();
128+
Set<Map.Entry<?, ?>> entrySet = map.entrySet();
123129
for (Map.Entry<?, ?> entry : entrySet) {
124130
out.put(wrapJavaObject(entry.getKey()), wrapJavaObject(entry.getValue()));
125131
}
126132
return out;
127133
}
128134

129-
if (object instanceof Class) {
130-
Class<?> maybeFunctionClass = (Class<?>) object;
135+
if (object instanceof Class maybeFunctionClass) {
131136
if (Set.of(maybeFunctionClass.getInterfaces()).contains(PythonLikeFunction.class)) {
132137
return new PythonCode((Class<? extends PythonLikeFunction>) maybeFunctionClass);
133138
}
134139
}
135140

136-
if (object instanceof OpaquePythonReference) {
137-
return new PythonObjectWrapper((OpaquePythonReference) object);
141+
if (object instanceof OpaquePythonReference opaquePythonReference) {
142+
return new PythonObjectWrapper(opaquePythonReference);
138143
}
139144

140145
// Default: return a JavaObjectWrapper
@@ -161,6 +166,10 @@ public static PythonLikeType getPythonLikeType(Class<?> javaClass) {
161166
return BuiltinTypes.INT_TYPE;
162167
}
163168

169+
if (BigDecimal.class.equals(javaClass) || PythonDecimal.class.equals(javaClass)) {
170+
return BuiltinTypes.DECIMAL_TYPE;
171+
}
172+
164173
if (float.class.equals(javaClass) || double.class.equals(javaClass) ||
165174
Float.class.equals(javaClass) || Double.class.equals(javaClass) ||
166175
PythonFloat.class.equals(javaClass)) {
@@ -273,7 +282,7 @@ public static <T> T convertPythonObjectToJavaType(Class<? extends T> type, Pytho
273282
PythonNumber pythonNumber = (PythonNumber) object;
274283
Number value = pythonNumber.getValue();
275284

276-
if (type.equals(BigInteger.class)) {
285+
if (type.equals(BigInteger.class) || type.equals(BigDecimal.class)) {
277286
return (T) value;
278287
}
279288

@@ -355,13 +364,23 @@ public static void returnValue(MethodVisitor methodVisitor, MethodDescriptor met
355364
Type.INT_TYPE.equals(returnAsmType) ||
356365
Type.LONG_TYPE.equals(returnAsmType) ||
357366
Type.FLOAT_TYPE.equals(returnAsmType) ||
358-
Type.DOUBLE_TYPE.equals(returnAsmType)) {
367+
Type.DOUBLE_TYPE.equals(returnAsmType) ||
368+
Type.getType(BigInteger.class).equals(returnAsmType) ||
369+
Type.getType(BigDecimal.class).equals(returnAsmType)) {
359370
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonNumber.class));
360371
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE,
361372
Type.getInternalName(PythonNumber.class),
362373
"getValue",
363374
Type.getMethodDescriptor(Type.getType(Number.class)),
364375
true);
376+
377+
if (Type.getType(BigInteger.class).equals(returnAsmType) ||
378+
Type.getType(BigDecimal.class).equals(returnAsmType)) {
379+
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, returnAsmType.getInternalName());
380+
methodVisitor.visitInsn(Opcodes.ARETURN);
381+
return;
382+
}
383+
365384
String wrapperClassName = null;
366385
String methodName = null;
367386
String methodDescriptor = null;

jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/BuiltinTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import ai.timefold.jpyinterpreter.types.collections.view.DictValueView;
2424
import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean;
2525
import ai.timefold.jpyinterpreter.types.numeric.PythonComplex;
26+
import ai.timefold.jpyinterpreter.types.numeric.PythonDecimal;
2627
import ai.timefold.jpyinterpreter.types.numeric.PythonFloat;
2728
import ai.timefold.jpyinterpreter.types.numeric.PythonInteger;
2829
import ai.timefold.jpyinterpreter.types.numeric.PythonNumber;
@@ -60,6 +61,7 @@ public class BuiltinTypes {
6061
public static final PythonLikeType BOOLEAN_TYPE = new PythonLikeType("bool", PythonBoolean.class, List.of(INT_TYPE));
6162
public static final PythonLikeType FLOAT_TYPE = new PythonLikeType("float", PythonFloat.class, List.of(NUMBER_TYPE));
6263
public final static PythonLikeType COMPLEX_TYPE = new PythonLikeType("complex", PythonComplex.class, List.of(NUMBER_TYPE));
64+
public final static PythonLikeType DECIMAL_TYPE = new PythonLikeType("Decimal", PythonDecimal.class, List.of(NUMBER_TYPE));
6365

6466
public static final PythonLikeType STRING_TYPE = new PythonLikeType("str", PythonString.class, List.of(BASE_TYPE));
6567
public static final PythonLikeType BYTES_TYPE = new PythonLikeType("bytes", PythonBytes.class, List.of(BASE_TYPE));

0 commit comments

Comments
 (0)