Skip to content

Commit fb19116

Browse files
authored
Merge pull request #523 from avinxshKD/fix/java-e2e-study-example-522
add java end-to-end study example
2 parents 3f8fe0f + 8e1c043 commit fb19116

5 files changed

Lines changed: 285 additions & 0 deletions

File tree

example/java_e2e/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Java end-to-end example
2+
3+
This example runs a Python controller (`controller.py`) and a Java PM node (`pm_java.java`) over the standard concore file-based exchange.
4+
5+
## Files
6+
7+
- `controller.py` - Python controller node
8+
- `pm_java.java` - Java PM node using `concoredocker.java`
9+
- `java_e2e.graphml` - workflow graph for the example
10+
- `smoke_check.py` - lightweight verification script
11+
12+
## Prerequisites
13+
14+
- Python environment with project dependencies installed
15+
- JDK (for `javac` and `java`)
16+
- `jeromq-0.6.0.jar`
17+
18+
Download jar (from repo root):
19+
20+
```bash
21+
mkdir -p .ci-cache/java
22+
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
23+
```
24+
25+
## Run smoke check
26+
27+
From repo root:
28+
29+
```bash
30+
python example/java_e2e/smoke_check.py --jar .ci-cache/java/jeromq-0.6.0.jar
31+
```
32+
33+
Expected result:
34+
35+
- script prints `smoke_check passed`
36+
- final `u` and `ym` payloads are printed in concore wire format

example/java_e2e/controller.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import ast
2+
import os
3+
from pathlib import Path
4+
5+
import concore
6+
import numpy as np
7+
8+
9+
ysp = 3.0
10+
11+
12+
def controller(ym):
13+
if ym[0] < ysp:
14+
return 1.01 * ym
15+
else:
16+
return 0.9 * ym
17+
18+
19+
study_dir = os.environ.get("CONCORE_STUDY_DIR", str(Path(__file__).resolve().parent / "study"))
20+
os.makedirs(os.path.join(study_dir, "1"), exist_ok=True)
21+
22+
concore.inpath = os.path.join(study_dir, "")
23+
concore.outpath = os.path.join(study_dir, "")
24+
concore.default_maxtime(20)
25+
concore.delay = 0.02
26+
27+
init_simtime_u = "[0.0, 0.0]"
28+
init_simtime_ym = "[0.0, 0.0]"
29+
30+
u = np.array([concore.initval(init_simtime_u)]).T
31+
while(concore.simtime<concore.maxtime):
32+
while concore.unchanged():
33+
ym = concore.read(1,"ym",init_simtime_ym)
34+
if isinstance(ym, tuple):
35+
ym, _ok = ym
36+
if isinstance(ym, str):
37+
ym = ast.literal_eval(ym)
38+
ym = np.array([ym]).T
39+
#####
40+
u = controller(ym)
41+
#####
42+
print(str(concore.simtime) + ". u="+str(u) + "ym="+str(ym));
43+
concore.write(1,"u",list(u.T[0]),delta=0)
44+
45+
print("retry="+str(concore.retrycount))

example/java_e2e/java_e2e.graphml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<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">
3+
<key for="node" id="d6" yfiles.type="nodegraphics"/>
4+
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
5+
<graph edgedefault="directed" id="java-e2e" projectName="java-e2e">
6+
<node id="controller-node">
7+
<data key="d6">
8+
<y:ShapeNode>
9+
<y:Geometry height="50" width="150" x="80" y="80"/>
10+
<y:Fill color="#ffcc00" opacity="1"/>
11+
<y:BorderStyle color="#000" width="1"/>
12+
<y:NodeLabel>CJ:example/java_e2e/controller.py</y:NodeLabel>
13+
<y:Shape type="rectangle"/>
14+
</y:ShapeNode>
15+
</data>
16+
</node>
17+
<node id="pm-node">
18+
<data key="d6">
19+
<y:ShapeNode>
20+
<y:Geometry height="50" width="170" x="80" y="240"/>
21+
<y:Fill color="#ffcc00" opacity="1"/>
22+
<y:BorderStyle color="#000" width="1"/>
23+
<y:NodeLabel>PJ:example/java_e2e/pm_java.java</y:NodeLabel>
24+
<y:Shape type="rectangle"/>
25+
</y:ShapeNode>
26+
</data>
27+
</node>
28+
<edge id="edge-cu" source="controller-node" target="pm-node">
29+
<data key="d10">
30+
<y:GenericEdge configuration="com.yworks.bpmn.Connection">
31+
<y:LineStyle color="#f44336" width="1" type="solid"/>
32+
<y:Arrows source="none" target="delta"/>
33+
<y:EdgeLabel>CU</y:EdgeLabel>
34+
</y:GenericEdge>
35+
</data>
36+
</edge>
37+
<edge id="edge-pym" source="pm-node" target="controller-node">
38+
<data key="d10">
39+
<y:GenericEdge configuration="com.yworks.bpmn.Connection">
40+
<y:LineStyle color="#827717" width="1" type="solid"/>
41+
<y:Arrows source="none" target="delta"/>
42+
<y:EdgeLabel>PYM</y:EdgeLabel>
43+
</y:GenericEdge>
44+
</data>
45+
</edge>
46+
</graph>
47+
</graphml>

example/java_e2e/pm_java.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import java.util.ArrayList;
2+
import java.util.List;
3+
4+
public class pm_java {
5+
private static final String INIT_SIMTIME_U = "[0.0, 0.0]";
6+
7+
private static double toDouble(Object value) {
8+
if (value instanceof Number) {
9+
return ((Number) value).doubleValue();
10+
}
11+
return 0.0;
12+
}
13+
14+
public static void main(String[] args) {
15+
String studyDir = System.getenv("CONCORE_STUDY_DIR");
16+
if (studyDir == null || studyDir.isEmpty()) {
17+
studyDir = "example/java_e2e/study";
18+
}
19+
double maxTime = 20.0;
20+
21+
concoredocker.setInPath(studyDir);
22+
concoredocker.setOutPath(studyDir);
23+
concoredocker.setDelay(20);
24+
concoredocker.defaultMaxTime(maxTime);
25+
26+
while (concoredocker.getSimtime() < maxTime) {
27+
concoredocker.ReadResult readResult = concoredocker.read(1, "u", INIT_SIMTIME_U);
28+
List<Object> u = readResult.data;
29+
30+
double u0 = 0.0;
31+
if (!u.isEmpty()) {
32+
u0 = toDouble(u.get(0));
33+
}
34+
35+
double ym0 = u0 + 0.01;
36+
List<Object> ym = new ArrayList<>();
37+
ym.add(ym0);
38+
39+
System.out.println(concoredocker.getSimtime() + ". u=" + u + " ym=" + ym);
40+
concoredocker.write(1, "ym", ym, 1);
41+
}
42+
}
43+
}

example/java_e2e/smoke_check.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import argparse
2+
import ast
3+
import os
4+
from pathlib import Path
5+
import shutil
6+
import subprocess
7+
import sys
8+
9+
10+
JEROMQ_URL = "https://repo1.maven.org/maven2/org/zeromq/jeromq/0.6.0/jeromq-0.6.0.jar"
11+
12+
13+
def parse_args():
14+
parser = argparse.ArgumentParser(description="Run Java e2e example smoke check")
15+
parser.add_argument("--jar", type=Path, help="Path to jeromq jar")
16+
parser.add_argument("--keep-study", action="store_true", help="Keep generated study files")
17+
return parser.parse_args()
18+
19+
def main():
20+
args = parse_args()
21+
here = Path(__file__).resolve().parent
22+
repo_root = here.parents[1]
23+
study_dir = here / "study"
24+
jar_path = args.jar or (repo_root / ".ci-cache" / "java" / "jeromq-0.6.0.jar")
25+
26+
if not jar_path.exists():
27+
raise FileNotFoundError(
28+
f"Missing jeromq jar at {jar_path}. Download from {JEROMQ_URL} or pass --jar."
29+
)
30+
31+
if study_dir.exists():
32+
shutil.rmtree(study_dir)
33+
(study_dir / "1").mkdir(parents=True, exist_ok=True)
34+
(study_dir / "1" / "concore.maxtime").write_text("20", encoding="utf-8")
35+
36+
subprocess.run(
37+
[
38+
"javac",
39+
"-cp",
40+
str(jar_path),
41+
str(repo_root / "concoredocker.java"),
42+
str(here / "pm_java.java"),
43+
],
44+
check=True,
45+
cwd=repo_root,
46+
)
47+
48+
env = os.environ.copy()
49+
env["CONCORE_STUDY_DIR"] = str(study_dir)
50+
classpath = os.pathsep.join([str(repo_root), str(here), str(jar_path)])
51+
52+
py_log = open(study_dir / "controller.log", "w", encoding="utf-8")
53+
java_log = open(study_dir / "pm_java.log", "w", encoding="utf-8")
54+
55+
py_proc = subprocess.Popen(
56+
[sys.executable, str(here / "controller.py")],
57+
cwd=repo_root,
58+
env=env,
59+
stdout=py_log,
60+
stderr=subprocess.STDOUT,
61+
)
62+
java_proc = subprocess.Popen(
63+
["java", "-cp", classpath, "pm_java"],
64+
cwd=repo_root,
65+
env=env,
66+
stdout=java_log,
67+
stderr=subprocess.STDOUT,
68+
)
69+
70+
try:
71+
py_rc = py_proc.wait(timeout=45)
72+
java_rc = java_proc.wait(timeout=45)
73+
except subprocess.TimeoutExpired:
74+
py_proc.kill()
75+
java_proc.kill()
76+
py_log.close()
77+
java_log.close()
78+
raise RuntimeError("Timed out waiting for node processes")
79+
80+
py_log.close()
81+
java_log.close()
82+
83+
if py_rc != 0 or java_rc != 0:
84+
raise RuntimeError(
85+
f"Node process failed (controller={py_rc}, pm_java={java_rc}). "
86+
f"See {study_dir / 'controller.log'} and {study_dir / 'pm_java.log'}."
87+
)
88+
89+
u_path = study_dir / "1" / "u"
90+
ym_path = study_dir / "1" / "ym"
91+
if not u_path.exists() or not ym_path.exists():
92+
raise RuntimeError("Expected output files were not produced")
93+
94+
u_val = ast.literal_eval(u_path.read_text(encoding="utf-8"))
95+
ym_val = ast.literal_eval(ym_path.read_text(encoding="utf-8"))
96+
if not isinstance(u_val, list) or len(u_val) < 2:
97+
raise RuntimeError("u output did not match expected wire format")
98+
if not isinstance(ym_val, list) or len(ym_val) < 2:
99+
raise RuntimeError("ym output did not match expected wire format")
100+
101+
print("smoke_check passed")
102+
print(f"u: {u_val}")
103+
print(f"ym: {ym_val}")
104+
105+
if not args.keep_study and study_dir.exists():
106+
shutil.rmtree(study_dir)
107+
108+
class_file = here / "pm_java.class"
109+
if class_file.exists():
110+
class_file.unlink()
111+
112+
113+
if __name__ == "__main__":
114+
main()

0 commit comments

Comments
 (0)