diff --git a/compiler/pom.xml b/compiler/pom.xml index 7dd737d32..5da727d8d 100644 --- a/compiler/pom.xml +++ b/compiler/pom.xml @@ -19,7 +19,7 @@ Copyright (C) 2024 gregory higgins (C) 2024 gregory higgins com.fluxtion root-parent-pom - 9.1.17-SNAPSHOT + 9.1.18-SNAPSHOT ../parent-root/pom.xml diff --git a/compiler/src/test/java/com/fluxtion/runtime/ml/RegressionTest.java b/compiler/src/test/java/com/fluxtion/runtime/ml/RegressionTest.java new file mode 100644 index 000000000..4bed33559 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/runtime/ml/RegressionTest.java @@ -0,0 +1,103 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.compiler.builder.dataflow.DataFlow; +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.ExportService; +import com.fluxtion.runtime.annotations.OnEventHandler; +import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.dataflow.FlowSupplier; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +public class RegressionTest extends MultipleSepTargetInProcessTest { + public RegressionTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void simpleTest() { + sep(c -> c.addNode(new PredictiveLinearRegressionModel(new AreaFeature()), "predictiveModel")); + + //initial prediction is NaN + PredictiveModel predictiveModel = getField("predictiveModel"); + Assert.assertTrue(Double.isNaN(predictiveModel.predictedValue())); + + //set calibration prediction is 0 + sep.getExportedService(CalibrationProcessor.class).setCalibration( + Arrays.asList( + Calibration.builder() + .featureClass(AreaFeature.class) + .weight(2).co_efficient(1.5) + .featureVersion(0) + .build())); + Assert.assertEquals(0, predictiveModel.predictedValue(), 0.000_1); + + //send record to generate a prediction + onEvent(new HouseDetails(12, 3)); + Assert.assertEquals(36, predictiveModel.predictedValue(), 0.000_1); + } + + @Test + public void subscribeTest() { + writeSourceFile = true; + sep(c -> { + FlowSupplier processedDouseDetails = DataFlow.subscribe(HouseDetails.class).flowSupplier(); + c.addNode(new PredictiveLinearRegressionModel(new AreaFeatureSubscribed(processedDouseDetails)), "predictiveModel"); + }); + + //initial prediction is NaN + PredictiveModel predictiveModel = getField("predictiveModel"); + Assert.assertTrue(Double.isNaN(predictiveModel.predictedValue())); + + //set calibration prediction is 0 + sep.getExportedService(CalibrationProcessor.class).setCalibration( + Arrays.asList( + Calibration.builder() + .featureClass(AreaFeatureSubscribed.class) + .weight(2) + .co_efficient(1.5) + .featureVersion(0) + .build())); + Assert.assertEquals(0, predictiveModel.predictedValue(), 0.000_1); + + //send record to generate a prediction + onEvent(new HouseDetails(12, 3)); + Assert.assertEquals(36, predictiveModel.predictedValue(), 0.000_1); + } + + + public static class AreaFeature extends AbstractFeature implements @ExportService CalibrationProcessor { + + @OnEventHandler + public boolean processRecord(HouseDetails houseDetails) { + value = houseDetails.area * co_efficient * weight; + return true; + } + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class AreaFeatureSubscribed extends AbstractFeature implements @ExportService CalibrationProcessor { + + private final FlowSupplier houseDetailSupplier; + + @OnTrigger + public boolean processRecord() { + value = houseDetailSupplier.get().area * co_efficient * weight; + return true; + } + + } + + @Value + public static class HouseDetails { + double area; + double distance; + } +} diff --git a/parent-root/pom.xml b/parent-root/pom.xml index 224bf64db..763a4cd38 100644 --- a/parent-root/pom.xml +++ b/parent-root/pom.xml @@ -21,7 +21,7 @@ 4.0.0 com.fluxtion root-parent-pom - 9.1.17-SNAPSHOT + 9.1.18-SNAPSHOT pom fluxtion :: poms :: parent root diff --git a/pom.xml b/pom.xml index 0596892b4..baaa6726a 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ along with this program. If not, see 4.0.0 com.fluxtion fluxtion.master - 9.1.17-SNAPSHOT + 9.1.18-SNAPSHOT pom fluxtion diff --git a/runtime/pom.xml b/runtime/pom.xml index a3706fb46..ef8fda037 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -20,7 +20,7 @@ Copyright (C) 2024 gregory higgins (C) 2024 gregory higgins com.fluxtion root-parent-pom - 9.1.17-SNAPSHOT + 9.1.18-SNAPSHOT ../parent-root/pom.xml diff --git a/runtime/src/main/java/com/fluxtion/runtime/annotations/feature/Experimental.java b/runtime/src/main/java/com/fluxtion/runtime/annotations/feature/Experimental.java new file mode 100644 index 000000000..d373cc0f7 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/annotations/feature/Experimental.java @@ -0,0 +1,11 @@ +package com.fluxtion.runtime.annotations.feature; + +/** + * Marks a class or method as a experimental feature. Mirrors the use of jdk experimental features: + *

+ * Experimental features represent early versions of (mostly) VM-level features, which can be risky, incomplete, or even + * unstable. In most cases, they need to be enabled using dedicated flags. For the purpose of comparison, if an + * experimental feature is considered 25% “done”, then a preview feature should be at least 95% “done” + */ +public @interface Experimental { +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/annotations/feature/Preview.java b/runtime/src/main/java/com/fluxtion/runtime/annotations/feature/Preview.java new file mode 100644 index 000000000..dcd85e1c7 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/annotations/feature/Preview.java @@ -0,0 +1,11 @@ +package com.fluxtion.runtime.annotations.feature; + +/** + * Marks a class or method as a preview feature. Mirrors the use of jdk preview features: + *

+ * A preview feature is a new feature of the Java language, Java Virtual Machine, or Java SE API that is fully specified, + * fully implemented, and yet impermanent. It is available in a JDK feature release to provoke developer feedback based + * on real world use; this may lead to it becoming permanent in a future Java SE Platform. + */ +public @interface Preview { +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/AbstractFeature.java b/runtime/src/main/java/com/fluxtion/runtime/ml/AbstractFeature.java new file mode 100644 index 000000000..d2dae9b4f --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/AbstractFeature.java @@ -0,0 +1,40 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.runtime.annotations.Initialise; +import com.fluxtion.runtime.annotations.feature.Experimental; + +import java.util.List; + +@Experimental +public abstract class AbstractFeature implements Feature, CalibrationProcessor { + + protected double co_efficient; + protected double weight; + protected double value; + + @Initialise + public void init() { + co_efficient = 0; + weight = 0; + value = 0; + } + + @Override + public boolean setCalibration(List calibrations) { + for (int i = 0, calibrationsSize = calibrations.size(); i < calibrationsSize; i++) { + Calibration calibration = calibrations.get(i); + if (calibration.getFeatureIdentifier().equals(identifier())) { + co_efficient = calibration.getCo_efficient(); + weight = calibration.getWeight(); + return true; + } + } + return false; + } + + @Override + public double value() { + return value; + } + +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/Calibration.java b/runtime/src/main/java/com/fluxtion/runtime/ml/Calibration.java new file mode 100644 index 000000000..4eab68a6a --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/Calibration.java @@ -0,0 +1,20 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.runtime.annotations.feature.Experimental; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@Experimental +public class Calibration { + private String featureIdentifier; + private Class featureClass; + private int featureVersion; + private double co_efficient; + private double weight; + + public String getFeatureIdentifier() { + return featureIdentifier == null ? featureClass.getSimpleName() : featureIdentifier; + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/CalibrationProcessor.java b/runtime/src/main/java/com/fluxtion/runtime/ml/CalibrationProcessor.java new file mode 100644 index 000000000..c78809d36 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/CalibrationProcessor.java @@ -0,0 +1,8 @@ +package com.fluxtion.runtime.ml; + +import java.util.List; + +public interface CalibrationProcessor { + + boolean setCalibration(List calibration); +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/Feature.java b/runtime/src/main/java/com/fluxtion/runtime/ml/Feature.java new file mode 100644 index 000000000..7d18ea78a --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/Feature.java @@ -0,0 +1,25 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.runtime.annotations.ExportService; +import com.fluxtion.runtime.annotations.feature.Experimental; +import com.fluxtion.runtime.node.NamedNode; + +@Experimental +public interface Feature extends NamedNode, @ExportService CalibrationProcessor { + + default String identifier() { + return getClass().getSimpleName(); + } + + default int version() { + return 0; + } + + @Override + default String getName() { + return identifier() + "_" + version(); + } + + double value(); + +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/MutableDouble.java b/runtime/src/main/java/com/fluxtion/runtime/ml/MutableDouble.java new file mode 100644 index 000000000..179f334ca --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/MutableDouble.java @@ -0,0 +1,20 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.runtime.annotations.feature.Experimental; + +@Experimental +public class MutableDouble { + double value; + + public MutableDouble(double value) { + this.value = value; + } + + public MutableDouble() { + this(Double.NaN); + } + + void reset() { + value = Double.NaN; + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/PredictiveLinearRegressionModel.java b/runtime/src/main/java/com/fluxtion/runtime/ml/PredictiveLinearRegressionModel.java new file mode 100644 index 000000000..b7974378e --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/PredictiveLinearRegressionModel.java @@ -0,0 +1,59 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.runtime.annotations.*; +import com.fluxtion.runtime.annotations.feature.Experimental; + +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +@Experimental +public class PredictiveLinearRegressionModel implements PredictiveModel, @ExportService CalibrationProcessor { + + private transient final Map valueMap; + private final Feature[] features; + private double prediction = Double.NaN; + + public PredictiveLinearRegressionModel(Feature... features) { + this.features = features; + this.valueMap = new IdentityHashMap<>(features.length); + for (Feature feature : features) { + valueMap.put(feature, new MutableDouble(0)); + } + } + + @Initialise + public void init() { + prediction = Double.NaN; + } + + @Override + @NoPropagateFunction + public boolean setCalibration(List calibrations) { + double previousValue = prediction; + prediction = 0; + for (Feature feature : features) { + prediction += feature.value(); + } + return previousValue != prediction | Double.isNaN(previousValue) != Double.isNaN(prediction); + } + + @OnParentUpdate + public void featureUpdated(Feature featureUpdated) { + MutableDouble previousValue = valueMap.get(featureUpdated); + double newValue = featureUpdated.value(); + prediction += newValue - previousValue.value; + previousValue.value = newValue; + } + + @OnTrigger + public boolean calculateInference() { + return true; + } + + + @Override + public double predictedValue() { + return prediction; + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/ml/PredictiveModel.java b/runtime/src/main/java/com/fluxtion/runtime/ml/PredictiveModel.java new file mode 100644 index 000000000..3b4f373b8 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/ml/PredictiveModel.java @@ -0,0 +1,9 @@ +package com.fluxtion.runtime.ml; + +import com.fluxtion.runtime.annotations.feature.Experimental; + +@Experimental +public interface PredictiveModel { + + double predictedValue(); +}