OptixLog Docs
Python SDKPipeline API

Building the Graph

Connect workflow nodes via origin and forward edges, chain multiple ingests, and use returned EntityRefs to thread a graph programmatically.

The project graph is built by wiring edges between nodes when you ingest them. You can do this with the prompt-guided shape (the server infers placement) or the explicit shape (you supply node ids directly).

Reading the IngestResult to thread subsequent nodes

Every ingest call returns an IngestResult with node_id and the resolved origin_nodes / forward_nodes as list[EntityRef]. Use the returned node_id to wire subsequent ingests.

chain.py
from optixlog_gen import OptixClient, Projects, SimulationNode, MeasurementNode

opt = OptixClient()
grating = opt.project(Projects.GRATING_COUPLER_LAB)

# Step 1: ingest the setup node (no predecessors)
setup_result = grating.ingest(
    data=SimulationNode(config={"mesh": "coarse"}, solver="fdtd"),
    origin_nodes=[],
    forward_nodes=[],
)
setup_id = setup_result.node_id  # e.g. "node-setup-abc123"

# Step 2: ingest a refinement node that originates from the setup
refine_result = grating.ingest(
    data=SimulationNode(config={"mesh": "fine"}, solver="fdtd"),
    origin_nodes=[setup_id],
    forward_nodes=[],
)
refine_id = refine_result.node_id

# Step 3: ingest a measurement that follows the fine simulation
measure_result = grating.ingest(
    data=MeasurementNode(metric="transmission", value=0.82),
    origin_nodes=[refine_id],
    forward_nodes=[],
)

Connecting to existing nodes

If previous nodes already exist in the project, fetch their ids from the Management API before ingesting:

connect_existing.py
from optixlog import SDKClient
from optixlog.management import Management
from optixlog_gen import OptixClient, Projects, MeasurementNode

client = SDKClient(base_url="https://optixlog.leidos.com", api_key="sk-opt-...")

# Find the most recent FDTD simulation
mgmt = Management(client)
project_mgmt = mgmt.project("proj_grating_7f3a")
sim_node = project_mgmt.workflow_nodes.first(
    label="SimulationRun",
    has_property={"solver": "fdtd"},
)

# Ingest a measurement downstream of it
opt = OptixClient()
grating = opt.project(Projects.GRATING_COUPLER_LAB)

result = grating.ingest(
    data=MeasurementNode(metric="transmission", value=0.82),
    origin_nodes=[sim_node.id],
    forward_nodes=[],
)
print(result.node_id, result.origin_nodes)

Branching and merging

A node can have multiple origins and multiple forward nodes. Pass all relevant ids in the lists:

branch_merge.py
# Merge two upstream nodes into one analysis node
analysis_result = grating.ingest(
    data=SimulationNode(config={}, solver="fdtd"),
    origin_nodes=["node-sim-a", "node-sim-b"],
    forward_nodes=[],
)

# Fork: one node feeds two downstream nodes (supply forward_nodes)
grating.ingest(
    data=SimulationNode(config={}, solver="eme"),
    origin_nodes=["node-setup-xyz"],
    forward_nodes=["node-analysis-1", "node-analysis-2"],
)

Using EntityRef from results

The origin_nodes and forward_nodes on IngestResult are list[EntityRef] with type, id, and name fields. Use them to inspect what edges the server resolved:

result = grating.ingest(
    data=SimulationNode(config={}, solver="fdtd"),
    origin_nodes=["node-abc"],
    forward_nodes=[],
)

for ref in result.origin_nodes:
    print(ref.type, ref.id, ref.name)
    # e.g. "workflow_node" "node-abc" "Coarse FDTD Setup"

Prompt-guided chaining

When using the prompt shape, you don't specify ids — but you still get node_id back, which you can use in subsequent explicit calls:

first = grating.ingest(
    data=SimulationNode(config={"mesh": "coarse"}, solver="fdtd"),
    prompt="Initial coarse FDTD simulation.",
)

# Use explicit placement for a follow-up that depends on the first
second = grating.ingest(
    data=SimulationNode(config={"mesh": "fine"}, solver="fdtd"),
    origin_nodes=[first.node_id],
    forward_nodes=[],
)

Idempotency

ingest creates a new node or updates an existing one depending on the server's matching logic for the given node type and payload. Check result.created to know whether a new node was created (True) or an existing one was updated (False).

On this page