Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions example/java_e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Java end-to-end example

This example runs a Python controller (`controller.py`) and a Java PM node (`pm_java.java`) over the standard concore file-based exchange.

## Files

- `controller.py` - Python controller node
- `pm_java.java` - Java PM node using `concoredocker.java`
- `java_e2e.graphml` - workflow graph for the example
- `smoke_check.py` - lightweight verification script

## Prerequisites

- Python environment with project dependencies installed
- JDK (for `javac` and `java`)
- `jeromq-0.6.0.jar`

Download jar (from repo root):

```bash
mkdir -p .ci-cache/java
curl -fsSL -o .ci-cache/java/jeromq-0.6.0.jar https://repo1.maven.org/maven2/org/zeromq/jeromq/0.6.0/jeromq-0.6.0.jar
```

## Run smoke check

From repo root:

```bash
python example/java_e2e/smoke_check.py --jar .ci-cache/java/jeromq-0.6.0.jar
```

Expected result:

- script prints `smoke_check passed`
- final `u` and `ym` payloads are printed in concore wire format
45 changes: 45 additions & 0 deletions example/java_e2e/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import ast
import os
from pathlib import Path

import concore
import numpy as np


ysp = 3.0


def controller(ym):
if ym[0] < ysp:
return 1.01 * ym
else:
return 0.9 * ym


study_dir = os.environ.get("CONCORE_STUDY_DIR", str(Path(__file__).resolve().parent / "study"))
os.makedirs(os.path.join(study_dir, "1"), exist_ok=True)

concore.inpath = os.path.join(study_dir, "")
concore.outpath = os.path.join(study_dir, "")
concore.default_maxtime(20)
concore.delay = 0.02

init_simtime_u = "[0.0, 0.0]"
init_simtime_ym = "[0.0, 0.0]"

u = np.array([concore.initval(init_simtime_u)]).T
while(concore.simtime<concore.maxtime):
while concore.unchanged():
ym = concore.read(1,"ym",init_simtime_ym)
if isinstance(ym, tuple):
ym, _ok = ym
if isinstance(ym, str):
ym = ast.literal_eval(ym)
ym = np.array([ym]).T
#####
u = controller(ym)
#####
print(str(concore.simtime) + ". u="+str(u) + "ym="+str(ym));
concore.write(1,"u",list(u.T[0]),delta=0)

print("retry="+str(concore.retrycount))
47 changes: 47 additions & 0 deletions example/java_e2e/java_e2e.graphml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd" xmlns:y="http://www.yworks.com/xml/graphml">
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="java-e2e" projectName="java-e2e">
<node id="controller-node">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50" width="150" x="80" y="80"/>
<y:Fill color="#ffcc00" opacity="1"/>
<y:BorderStyle color="#000" width="1"/>
<y:NodeLabel>CJ:example/java_e2e/controller.py</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="pm-node">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="50" width="170" x="80" y="240"/>
<y:Fill color="#ffcc00" opacity="1"/>
<y:BorderStyle color="#000" width="1"/>
<y:NodeLabel>PJ:example/java_e2e/pm_java.java</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<edge id="edge-cu" source="controller-node" target="pm-node">
<data key="d10">
<y:GenericEdge configuration="com.yworks.bpmn.Connection">
<y:LineStyle color="#f44336" width="1" type="solid"/>
<y:Arrows source="none" target="delta"/>
<y:EdgeLabel>CU</y:EdgeLabel>
</y:GenericEdge>
</data>
</edge>
<edge id="edge-pym" source="pm-node" target="controller-node">
<data key="d10">
<y:GenericEdge configuration="com.yworks.bpmn.Connection">
<y:LineStyle color="#827717" width="1" type="solid"/>
<y:Arrows source="none" target="delta"/>
<y:EdgeLabel>PYM</y:EdgeLabel>
</y:GenericEdge>
</data>
</edge>
</graph>
</graphml>
43 changes: 43 additions & 0 deletions example/java_e2e/pm_java.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import java.util.ArrayList;
import java.util.List;

public class pm_java {
private static final String INIT_SIMTIME_U = "[0.0, 0.0]";

private static double toDouble(Object value) {
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return 0.0;
}

public static void main(String[] args) {
String studyDir = System.getenv("CONCORE_STUDY_DIR");
if (studyDir == null || studyDir.isEmpty()) {
studyDir = "example/java_e2e/study";
}
double maxTime = 20.0;

concoredocker.setInPath(studyDir);
concoredocker.setOutPath(studyDir);
concoredocker.setDelay(20);
concoredocker.defaultMaxTime(maxTime);

while (concoredocker.getSimtime() < maxTime) {
concoredocker.ReadResult readResult = concoredocker.read(1, "u", INIT_SIMTIME_U);
List<Object> u = readResult.data;

double u0 = 0.0;
if (!u.isEmpty()) {
u0 = toDouble(u.get(0));
}

double ym0 = u0 + 0.01;
List<Object> ym = new ArrayList<>();
ym.add(ym0);

System.out.println(concoredocker.getSimtime() + ". u=" + u + " ym=" + ym);
concoredocker.write(1, "ym", ym, 1);
}
}
}
114 changes: 114 additions & 0 deletions example/java_e2e/smoke_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import argparse
import ast
import os
from pathlib import Path
import shutil
import subprocess
import sys


JEROMQ_URL = "https://repo1.maven.org/maven2/org/zeromq/jeromq/0.6.0/jeromq-0.6.0.jar"


def parse_args():
parser = argparse.ArgumentParser(description="Run Java e2e example smoke check")
parser.add_argument("--jar", type=Path, help="Path to jeromq jar")
parser.add_argument("--keep-study", action="store_true", help="Keep generated study files")
return parser.parse_args()

def main():
args = parse_args()
here = Path(__file__).resolve().parent
repo_root = here.parents[1]
study_dir = here / "study"
jar_path = args.jar or (repo_root / ".ci-cache" / "java" / "jeromq-0.6.0.jar")

if not jar_path.exists():
raise FileNotFoundError(
f"Missing jeromq jar at {jar_path}. Download from {JEROMQ_URL} or pass --jar."
)

if study_dir.exists():
shutil.rmtree(study_dir)
(study_dir / "1").mkdir(parents=True, exist_ok=True)
(study_dir / "1" / "concore.maxtime").write_text("20", encoding="utf-8")

subprocess.run(
[
"javac",
"-cp",
str(jar_path),
str(repo_root / "concoredocker.java"),
str(here / "pm_java.java"),
],
check=True,
cwd=repo_root,
)

env = os.environ.copy()
env["CONCORE_STUDY_DIR"] = str(study_dir)
classpath = os.pathsep.join([str(repo_root), str(here), str(jar_path)])

py_log = open(study_dir / "controller.log", "w", encoding="utf-8")
java_log = open(study_dir / "pm_java.log", "w", encoding="utf-8")

py_proc = subprocess.Popen(
[sys.executable, str(here / "controller.py")],
cwd=repo_root,
env=env,
stdout=py_log,
stderr=subprocess.STDOUT,
)
java_proc = subprocess.Popen(
["java", "-cp", classpath, "pm_java"],
cwd=repo_root,
env=env,
stdout=java_log,
stderr=subprocess.STDOUT,
)

try:
py_rc = py_proc.wait(timeout=45)
java_rc = java_proc.wait(timeout=45)
except subprocess.TimeoutExpired:
py_proc.kill()
java_proc.kill()
py_log.close()
java_log.close()
raise RuntimeError("Timed out waiting for node processes")

py_log.close()
java_log.close()

if py_rc != 0 or java_rc != 0:
raise RuntimeError(
f"Node process failed (controller={py_rc}, pm_java={java_rc}). "
f"See {study_dir / 'controller.log'} and {study_dir / 'pm_java.log'}."
)

u_path = study_dir / "1" / "u"
ym_path = study_dir / "1" / "ym"
if not u_path.exists() or not ym_path.exists():
raise RuntimeError("Expected output files were not produced")

u_val = ast.literal_eval(u_path.read_text(encoding="utf-8"))
ym_val = ast.literal_eval(ym_path.read_text(encoding="utf-8"))
if not isinstance(u_val, list) or len(u_val) < 2:
raise RuntimeError("u output did not match expected wire format")
if not isinstance(ym_val, list) or len(ym_val) < 2:
raise RuntimeError("ym output did not match expected wire format")

print("smoke_check passed")
print(f"u: {u_val}")
print(f"ym: {ym_val}")

if not args.keep_study and study_dir.exists():
shutil.rmtree(study_dir)

class_file = here / "pm_java.class"
if class_file.exists():
class_file.unlink()


if __name__ == "__main__":
main()
Loading