Implement a method in Java and use it in Python#
This guide walks through getting a Java class onto the classpath so you can call it from Python, using a small worked example: AlwaysPositive, a trivialmoa.classifiers.Classifier that always predicts one class.
For a full
walkthrough of AbstractClassifier and the methods implemented below
(trainOnInstanceImpl, getVotesForInstance, and so on), see MOA’s
Introduction to the API of
MOA
tutorial.
package example;
import com.github.javacliparser.IntOption;
import com.yahoo.labs.samoa.instances.Instance;
import moa.classifiers.AbstractClassifier;
import moa.classifiers.Classifier;
import moa.core.Measurement;
public class AlwaysPositive extends AbstractClassifier implements Classifier {
private static final long serialVersionUID = 1L;
public IntOption targetClassOption = new IntOption(
"targetClass", 'c',
"Class index this classifier always predicts.", 0, 0, Integer.MAX_VALUE);
@Override
public String getPurposeString() {
return "A trivial classifier that always predicts one configurable class.";
}
@Override
public void resetLearningImpl() {
// No state to reset.
}
@Override
public void trainOnInstanceImpl(Instance inst) {
// Never updates.
}
@Override
public double[] getVotesForInstance(Instance inst) {
double[] votes = new double[inst.numClasses()];
votes[targetClassOption.getValue()] = 1.0;
return votes;
}
@Override
public boolean isRandomizable() {
return false;
}
@Override
protected Measurement[] getModelMeasurementsImpl() {
return null;
}
@Override
public void getModelDescription(StringBuilder out, int indent) {
out.append("AlwaysPositive: always predicts class ")
.append(targetClassOption.getValue())
.append('.');
}
}
targetClassOption uses MOA’s options system
(com.github.javacliparser), the same mechanism every built-in MOA
learner uses for configurable parameters. Its short flag, 'c', becomes
the -c argument in a MOA CLI string.
There are two ways to get a class like this onto CapyMOA’s classpath: build all of MOA, or add just this one class to the classpath. Prefer building MOA if you plan to upstream your change into the MOA project. Use the classpath approach for prototypes or standalone research.
Approach 1: build the whole MOA project#
Use this when your Java change spans multiple classes, touches MOA’s own build, or you want the most faithful test.
Add
AlwaysPositive.javato your MOA source checkout, following its existing package layout (for example, alongsidemoa.classifiers.functions.MajorityClass, which CapyMOA already wraps insrc/capymoa/classifier/_majority_class.py).Build MOA with its own build tooling to produce a jar.
Point CapyMOA at your built jar instead of the bundled one:
export CAPYMOA_MOA_JAR=/path/to/your/moa/build/output/moa.jar python -c "import capymoa; capymoa.about()"
capymoa.about()prints the resolvedCAPYMOA_MOA_JARpath and a hash of the jar, so you can confirm CapyMOA picked up your build:$ python -c "import capymoa; capymoa.about()" CapyMOA 0.14.0 CAPYMOA_DATASETS_DIR: data CAPYMOA_MOA_JAR: /path/to/your/moa/build/output/moa.jar CAPYMOA_JVM_ARGS: ['-Xmx8g', '-Xss10M'] JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64 MOA version: <hash of your build> JAVA version: 21.0.11
Remember to unset CAPYMOA_MOA_JAR (or open a new shell) once you’re done,
so CapyMOA goes back to using the bundled jar.
Approach 2: put a single class on the classpath#
Use this when you’re iterating on one new Java class and don’t want to rebuild MOA.
Compile your class, linking against the bundled
moa.jarso thatAbstractClassifierresolves:mkdir -p /tmp/myclasses javac -cp src/capymoa/jar/moa.jar \ -d /tmp/myclasses \ example/AlwaysPositive.java
If you use an IDE, you can add a java archive as a dependency through the correct dialogue: InteliJ, Eclipse, VS Code.
The source file must live at
example/AlwaysPositive.java, matching itspackage example;declaration.Add the compiled output directory to the standard
CLASSPATHenvironment variable, then run Python as usual:export CLASSPATH=/tmp/myclasses python -c " import capymoa from example import AlwaysPositive print(AlwaysPositive) "
Note
If you prefer not to set an environment variable, call
jpype.addClassPath() before import capymoa instead. CapyMOA starts the
JVM on its first import, so the call has to come first:
import jpype
jpype.addClassPath("/tmp/myclasses")
import capymoa # must come after addClassPath
from example import AlwaysPositive
Wrap it in Python and run it#
Once your Java class is importable, subclass capymoa.base.MOAClassifier
and pass the Java class as moa_learner. Set options like
targetClassOption with a MOA CLI string passed as CLI. See
capymoa.classifier.StochasticGradientTree for a classifier that
configures several options this way.
from capymoa.base import MOAClassifier
from capymoa.datasets import ElectricityTiny
from capymoa.evaluation import prequential_evaluation
class AlwaysPositiveClassifier(MOAClassifier):
def __init__(self, schema, target_class: int = 0):
super().__init__(
moa_learner=AlwaysPositive,
schema=schema,
CLI=f"-c {target_class}",
)
stream = ElectricityTiny()
learner = AlwaysPositiveClassifier(stream.get_schema(), target_class=1)
results = prequential_evaluation(stream, learner, max_instances=1000)
print(results["cumulative"].accuracy())
From here, follow the FAQ for where the permanent wrapper file should live, which base class to use for regressors and anomaly detectors, and how to add tests and docstrings. Once your Java change is merged upstream in MOA, follow the Update MOA guide to bump the version CapyMOA bundles.