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 extends Feature> 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();
+}