Coverage for skema/program_analysis/CAST2FN/ann_cast/to_grfn_pass.py: 16%

356 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-30 17:15 +0000

1import typing 

2from functools import singledispatchmethod 

3 

4import networkx as nx 

5from skema.model_assembly.metadata import LambdaType 

6from skema.model_assembly.networks import ( 

7 GenericNode, 

8 GrFNLoopSubgraph, 

9 GrFNSubgraph, 

10 GroundedFunctionNetwork, 

11 HyperEdge, 

12 LambdaNode, 

13 LoopTopInterface, 

14 UnpackNode, 

15 PackNode, 

16) 

17from skema.model_assembly.sandbox import load_lambda_function 

18from skema.model_assembly.structures import ContainerIdentifier 

19from skema.program_analysis.CAST2FN.ann_cast.ann_cast_helpers import ( 

20 ELSEBODY, 

21 IFBODY, 

22 MODULE_SCOPE, 

23 GrfnAssignment, 

24 call_container_name, 

25 con_scope_to_str, 

26 create_container_metadata, 

27 is_func_def_main, 

28 lambda_var_from_fullid, 

29) 

30from skema.program_analysis.CAST2FN.ann_cast.annotated_cast import * 

31from skema.program_analysis.CAST2FN.model.cast import ( 

32 ScalarType, 

33 ValueConstructor, 

34) 

35 

36 

37class ToGrfnPass: 

38 def __init__(self, pipeline_state: PipelineState): 

39 self.pipeline_state = pipeline_state 

40 self.nodes = self.pipeline_state.nodes 

41 self.network = nx.DiGraph() 

42 self.subgraphs = nx.DiGraph() 

43 self.hyper_edges = [] 

44 

45 # populate network with variable nodes 

46 for grfn_var in self.pipeline_state.grfn_id_to_grfn_var.values(): 

47 self.network.add_node(grfn_var, **grfn_var.get_kwargs()) 

48 

49 # the fullid of a AnnCastName node is a string which includes its 

50 # variable name, numerical id, version, and scope 

51 for node in self.pipeline_state.nodes: 

52 self.visit(node, subgraph=None) 

53 

54 # build GrFN 

55 grfn_uid = GenericNode.create_node_id() 

56 timestamp = "timestamp" 

57 type_defs = [] 

58 metadata = [] 

59 ns = "default-ns" 

60 scope = "default" 

61 con_name = "GrFN" 

62 identifier = ContainerIdentifier(ns, scope, con_name) 

63 

64 # store GrFN in PipelineState 

65 self.pipeline_state.grfn = GroundedFunctionNetwork( 

66 grfn_uid, 

67 identifier, 

68 timestamp, 

69 self.network, 

70 self.hyper_edges, 

71 self.subgraphs, 

72 type_defs, 

73 metadata, 

74 ) 

75 

76 def grfn_vars_from_fullids(self, fullids: typing.Iterable): 

77 """ 

78 Return the list of GrFN Variables that are associated with the fullids 

79 from `fullids` 

80 Paramters: 

81 - `fullids`: an iterable of fullids 

82 """ 

83 grfn_vars = [] 

84 for fullid in fullids: 

85 grfn_var = self.pipeline_state.get_grfn_var(fullid) 

86 grfn_vars.append(grfn_var) 

87 

88 return grfn_vars 

89 

90 def create_interface_node(self, lambda_expr): 

91 # we should never create an interface node if we have an empty lambda expr 

92 assert len(lambda_expr) > 0 

93 lambda_uuid = GenericNode.create_node_id() 

94 lambda_str = lambda_expr 

95 lambda_func = load_lambda_function(lambda_str) 

96 # FUTURE: decide on metadata for interface nodes 

97 lambda_metadata = [] 

98 lambda_type = LambdaType.INTERFACE 

99 

100 interface_node = LambdaNode( 

101 lambda_uuid, lambda_type, lambda_str, lambda_func, lambda_metadata 

102 ) 

103 

104 return interface_node 

105 

106 def create_loop_top_interface(self, lambda_expr): 

107 # we should never create an interface node if we have an empty lambda expr 

108 assert len(lambda_expr) > 0 

109 lambda_uuid = GenericNode.create_node_id() 

110 lambda_str = lambda_expr 

111 lambda_func = load_lambda_function(lambda_str) 

112 # FUTURE: decide on metadata for interface nodes 

113 lambda_metadata = [] 

114 lambda_type = LambdaType.LOOP_TOP_INTERFACE 

115 

116 interface_node = LoopTopInterface( 

117 lambda_uuid, lambda_type, lambda_str, lambda_func, lambda_metadata 

118 ) 

119 

120 return interface_node 

121 

122 def add_grfn_edges( 

123 self, inputs: typing.List, lambda_node, outputs: typing.List 

124 ): 

125 """Parameters: 

126 - `inputs` and `outputs` are lists of GrFN VariableNode's 

127 - `lambda_node` is a GrFN LambdaNode 

128 

129 For each input in `inputs`, adds an edge from input to `lambda_node` 

130 For each output in `outputs`, adds an edge from `lambda_node` to output 

131 Adds a `HyperEdge` between `inputs`, `lambda_node`, and `outputs` 

132 """ 

133 # build input edge set 

134 input_edges = zip(inputs, [lambda_node] * len(inputs)) 

135 # build output edge set 

136 output_edges = zip([lambda_node] * len(outputs), outputs) 

137 # add edges to network 

138 self.network.add_edges_from(input_edges) 

139 self.network.add_edges_from(output_edges) 

140 # add HyperEdges to GrFN 

141 self.hyper_edges.append(HyperEdge(inputs, lambda_node, outputs)) 

142 

143 def create_condition_node( 

144 self, condition_in, condition_out, lambda_expr, subgraph: GrFNSubgraph 

145 ): 

146 lambda_uuid = GenericNode.create_node_id() 

147 lambda_str = lambda_expr 

148 lambda_func = load_lambda_function(lambda_str) 

149 # FUTURE: decide on metadata for condition nodes 

150 lambda_metadata = [] 

151 lambda_type = LambdaType.CONDITION 

152 

153 condition_node = LambdaNode( 

154 lambda_uuid, lambda_type, lambda_str, lambda_func, lambda_metadata 

155 ) 

156 self.network.add_node(condition_node, **condition_node.get_kwargs()) 

157 

158 inputs = self.grfn_vars_from_fullids(condition_in.values()) 

159 outputs = self.grfn_vars_from_fullids(condition_out.values()) 

160 self.add_grfn_edges(inputs, condition_node, outputs) 

161 

162 # add nodes to subgraph 

163 subgraph.nodes.extend(inputs + [condition_node] + outputs) 

164 

165 def create_decision_node( 

166 self, 

167 decision_in, 

168 decision_out, 

169 condition_var, 

170 lambda_expr, 

171 subgraph: GrFNSubgraph, 

172 ): 

173 lambda_uuid = GenericNode.create_node_id() 

174 lambda_str = lambda_expr 

175 lambda_func = load_lambda_function(lambda_str) 

176 # FUTURE: decide on metadata for decision nodes 

177 lambda_metadata = [] 

178 lambda_type = LambdaType.DECISION 

179 

180 decision_node = LambdaNode( 

181 lambda_uuid, lambda_type, lambda_str, lambda_func, lambda_metadata 

182 ) 

183 self.network.add_node(decision_node, **decision_node.get_kwargs()) 

184 

185 # FUTURE: modifying the order grfn_vars are added 

186 # to inputs may be necessary to perform correct execution 

187 # For now, we are following the pattern in `lambda_for_decision()` of 

188 # lambda COND, x_if, y_if, x_else, y_else: (x_if, y_if) if COND else (x_else, y_else) 

189 

190 # values for decision in are two element dicts with keys IFBODY and ELSEBODY 

191 if_body_dict = {} 

192 else_body_dict = {} 

193 for var_id, fullid in decision_in.items(): 

194 if_body_dict[var_id] = fullid[IFBODY] 

195 else_body_dict[var_id] = fullid[ELSEBODY] 

196 

197 if_body_inputs = self.grfn_vars_from_fullids(if_body_dict.values()) 

198 else_body_inputs = self.grfn_vars_from_fullids(else_body_dict.values()) 

199 

200 # concatenate if and else inputs after condition_var input to follow pattern 

201 inputs = [condition_var] + if_body_inputs + else_body_inputs 

202 outputs = self.grfn_vars_from_fullids(decision_out.values()) 

203 self.add_grfn_edges(inputs, decision_node, outputs) 

204 

205 # add nodes to subraph 

206 subgraph.nodes.extend(inputs + [decision_node] + outputs) 

207 

208 def visit_grfn_assignment( 

209 self, grfn_assignment: GrfnAssignment, subgraph: GrFNSubgraph 

210 ): 

211 assignment_node = grfn_assignment.assignment_node 

212 # update func_str and function for assignment node 

213 assignment_node.func_str = grfn_assignment.lambda_expr 

214 assignment_node.function = load_lambda_function( 

215 assignment_node.func_str 

216 ) 

217 

218 self.network.add_node(assignment_node, **assignment_node.get_kwargs()) 

219 

220 inputs = self.grfn_vars_from_fullids(grfn_assignment.inputs.keys()) 

221 outputs = self.grfn_vars_from_fullids(grfn_assignment.outputs.keys()) 

222 self.add_grfn_edges(inputs, assignment_node, outputs) 

223 

224 # Create strings representing the inputs and outputs for pack and unpack 

225 if isinstance(assignment_node, (PackNode, UnpackNode)): 

226 assignment_node.inputs = ",".join( 

227 list( 

228 map(lambda_var_from_fullid, grfn_assignment.inputs.keys()) 

229 ) 

230 ) 

231 assignment_node.output = ",".join( 

232 list( 

233 map(lambda_var_from_fullid, grfn_assignment.outputs.keys()) 

234 ) 

235 ) 

236 

237 # add subgraph nodes 

238 subgraph.nodes.extend(inputs + [assignment_node] + outputs) 

239 

240 def visit(self, node: AnnCastNode, subgraph: GrFNSubgraph): 

241 """ 

242 External visit that callsthe internal visit 

243 Useful for debugging/development. For example, 

244 printing the nodes that are visited 

245 """ 

246 # print current node being visited. 

247 # this can be useful for debugging 

248 # class_name = node.__class__.__name__ 

249 # print(f"\nProcessing node type {class_name}") 

250 

251 # call internal visit 

252 return self._visit(node, subgraph) 

253 

254 def visit_node_list( 

255 self, node_list: typing.List[AnnCastNode], subgraph: GrFNSubgraph 

256 ): 

257 return [self.visit(node, subgraph) for node in node_list] 

258 

259 @singledispatchmethod 

260 def _visit(self, node: AnnCastNode, subgraph: GrFNSubgraph): 

261 """ 

262 Internal visit 

263 """ 

264 raise NameError(f"Unrecognized node type: {type(node)}") 

265 

266 @_visit.register 

267 def visit_assignment( 

268 self, node: AnnCastAssignment, subgraph: GrFNSubgraph 

269 ): 

270 self.visit(node.right, subgraph) 

271 self.visit_grfn_assignment(node.grfn_assignment, subgraph) 

272 

273 @_visit.register 

274 def visit_attribute(self, node: AnnCastAttribute, subgraph: GrFNSubgraph): 

275 pass 

276 

277 @_visit.register 

278 def visit_call(self, node: AnnCastCall, subgraph: GrFNSubgraph): 

279 if node.is_grfn_2_2: 

280 self.visit_call_grfn_2_2(node, subgraph) 

281 return 

282 

283 self.visit_node_list(node.arguments, subgraph) 

284 for index, assignment in node.arg_assignments.items(): 

285 self.visit_grfn_assignment(assignment, subgraph) 

286 

287 parent = subgraph 

288 # make a new subgraph for this If Container 

289 type = "CallContainer" 

290 border_color = GrFNSubgraph.get_border_color(type) 

291 metadata = create_container_metadata(node.grfn_con_src_ref) 

292 nodes = [] 

293 parent_str = parent.uid if parent is not None else None 

294 occs = 0 

295 uid = GenericNode.create_node_id() 

296 ns = "default-ns" 

297 scope = con_scope_to_str( 

298 node.func.con_scope + [call_container_name(node)] 

299 ) 

300 basename = node.func.name # change from 'scope' to its function name 

301 basename_id = ( 

302 node.func.id 

303 ) # NOTE: represents the function's name ID, not the call number (i.e. invocation index) 

304 subgraph = GrFNSubgraph( 

305 uid, 

306 ns, 

307 scope, 

308 basename, 

309 basename_id, 

310 occs, 

311 parent_str, 

312 type, 

313 border_color, 

314 nodes, 

315 metadata, 

316 ) 

317 

318 self.subgraphs.add_node(subgraph) 

319 self.subgraphs.add_edge(parent, subgraph) 

320 

321 # build top interface if needed 

322 if len(node.top_interface_in) > 0: 

323 top_interface = self.create_interface_node( 

324 node.top_interface_lambda 

325 ) 

326 self.network.add_node(top_interface, **top_interface.get_kwargs()) 

327 

328 inputs = self.grfn_vars_from_fullids( 

329 node.top_interface_in.values() 

330 ) 

331 outputs = self.grfn_vars_from_fullids( 

332 node.top_interface_out.values() 

333 ) 

334 self.add_grfn_edges(inputs, top_interface, outputs) 

335 

336 # container includes top_interface and top_interface outputs 

337 subgraph.nodes.extend([top_interface] + outputs) 

338 

339 # build bot interface if needed 

340 if len(node.bot_interface_in) > 0: 

341 bot_interface = self.create_interface_node( 

342 node.bot_interface_lambda 

343 ) 

344 self.network.add_node(bot_interface, **bot_interface.get_kwargs()) 

345 

346 inputs = self.grfn_vars_from_fullids( 

347 node.bot_interface_in.values() 

348 ) 

349 outputs = self.grfn_vars_from_fullids( 

350 node.bot_interface_out.values() 

351 ) 

352 self.add_grfn_edges(inputs, bot_interface, outputs) 

353 

354 # container includes input and bot interface 

355 # the outputs need to be added to the parent subgraph 

356 subgraph.nodes.extend(inputs + [bot_interface]) 

357 parent.nodes.extend(outputs) 

358 

359 def visit_call_grfn_2_2(self, node: AnnCastCall, subgraph: GrFNSubgraph): 

360 # assert isinstance(node.func, AnnCastName) 

361 self.visit_node_list(node.arguments, subgraph) 

362 for assignment in node.arg_assignments.values(): 

363 self.visit_grfn_assignment(assignment, subgraph) 

364 

365 parent = subgraph 

366 # make a new subgraph for this If Container 

367 type = "FuncContainer" 

368 border_color = GrFNSubgraph.get_border_color(type) 

369 metadata = create_container_metadata( 

370 node.func_def_copy.grfn_con_src_ref 

371 ) 

372 nodes = [] 

373 parent_str = parent.uid if parent is not None else None 

374 occs = node.invocation_index 

375 uid = GenericNode.create_node_id() 

376 ns = "default-ns" 

377 scope = con_scope_to_str( 

378 node.func.con_scope + [call_container_name(node)] 

379 ) 

380 basename = node.func.name 

381 basename_id = node.func.id 

382 subgraph = GrFNSubgraph( 

383 uid, 

384 ns, 

385 scope, 

386 basename, 

387 basename_id, 

388 occs, 

389 parent_str, 

390 type, 

391 border_color, 

392 nodes, 

393 metadata, 

394 ) 

395 self.subgraphs.add_node(subgraph) 

396 self.subgraphs.add_edge(parent, subgraph) 

397 

398 # build top interface 

399 if len(node.top_interface_in) > 0: 

400 top_interface = self.create_interface_node( 

401 node.top_interface_lambda 

402 ) 

403 self.network.add_node(top_interface, **top_interface.get_kwargs()) 

404 

405 inputs = self.grfn_vars_from_fullids( 

406 node.top_interface_in.values() 

407 ) 

408 outputs = self.grfn_vars_from_fullids( 

409 node.top_interface_out.values() 

410 ) 

411 self.add_grfn_edges(inputs, top_interface, outputs) 

412 

413 # container includes top_interface and top_interface outputs 

414 subgraph.nodes.extend([top_interface] + outputs) 

415 

416 self.visit_function_def_copy(node.func_def_copy, subgraph) 

417 

418 # build bot interface 

419 if len(node.bot_interface_in) > 0: 

420 bot_interface = self.create_interface_node( 

421 node.bot_interface_lambda 

422 ) 

423 self.network.add_node(bot_interface, **bot_interface.get_kwargs()) 

424 

425 inputs = self.grfn_vars_from_fullids( 

426 node.bot_interface_in.values() 

427 ) 

428 outputs = self.grfn_vars_from_fullids( 

429 node.bot_interface_out.values() 

430 ) 

431 self.add_grfn_edges(inputs, bot_interface, outputs) 

432 

433 # container includes input and bot interface 

434 # the outputs need to be added to the parent subgraph 

435 subgraph.nodes.extend(inputs + [bot_interface]) 

436 parent.nodes.extend(outputs) 

437 

438 @_visit.register 

439 def visit_record_def(self, node: AnnCastRecordDef, subgraph: GrFNSubgraph): 

440 pass 

441 

442 def visit_function_def_copy( 

443 self, node: AnnCastFunctionDef, subgraph: GrFNSubgraph 

444 ): 

445 for dummy_assignment in node.dummy_grfn_assignments: 

446 self.visit_grfn_assignment(dummy_assignment, subgraph) 

447 

448 self.visit_node_list(node.func_args, subgraph) 

449 self.visit_node_list(node.body, subgraph) 

450 

451 @_visit.register 

452 def visit_function_def( 

453 self, node: AnnCastFunctionDef, subgraph: GrFNSubgraph 

454 ): 

455 # for GrFN 2.2, we create function containers at call sites, 

456 # so we skip all functions except "main" 

457 if self.pipeline_state.GENERATE_GRFN_2_2 and not is_func_def_main( 

458 node 

459 ): 

460 return 

461 

462 parent = subgraph 

463 type = "FuncContainer" 

464 border_color = GrFNSubgraph.get_border_color(type) 

465 metadata = create_container_metadata(node.grfn_con_src_ref) 

466 nodes = [] 

467 parent_str = parent.uid if parent is not None else None 

468 occs = 0 

469 uid = GenericNode.create_node_id() 

470 ns = "default-ns" 

471 scope = con_scope_to_str(node.con_scope) 

472 basename = ( 

473 node.name.name 

474 ) # was originally assigned to 'scope', changed to node.name.name 

475 basename_id = node.name.id # added 

476 subgraph = GrFNSubgraph( 

477 uid, 

478 ns, 

479 scope, 

480 basename, 

481 basename_id, 

482 occs, 

483 parent_str, 

484 type, 

485 border_color, 

486 nodes, 

487 metadata, 

488 ) 

489 

490 self.visit_node_list(node.func_args, subgraph) 

491 

492 # build top interface if needed 

493 if len(node.top_interface_in) > 0: 

494 top_interface = self.create_interface_node( 

495 node.top_interface_lambda 

496 ) 

497 self.network.add_node(top_interface, **top_interface.get_kwargs()) 

498 

499 inputs = self.grfn_vars_from_fullids( 

500 node.top_interface_in.values() 

501 ) 

502 outputs = self.grfn_vars_from_fullids( 

503 node.top_interface_out.values() 

504 ) 

505 self.add_grfn_edges(inputs, top_interface, outputs) 

506 

507 # add inputs to parent graph 

508 parent.nodes.extend(inputs) 

509 # add interface node and outputs to subraph 

510 subgraph.nodes.extend([top_interface] + outputs) 

511 

512 # visit dummy assignments before body 

513 for dummy_assignment in node.dummy_grfn_assignments: 

514 self.visit_grfn_assignment(dummy_assignment, subgraph) 

515 

516 # visit body 

517 self.visit_node_list(node.body, subgraph) 

518 

519 # build bot interface if needed 

520 if len(node.bot_interface_in) > 0: 

521 bot_interface = self.create_interface_node( 

522 node.bot_interface_lambda 

523 ) 

524 self.network.add_node(bot_interface, **bot_interface.get_kwargs()) 

525 

526 inputs = self.grfn_vars_from_fullids( 

527 node.bot_interface_in.values() 

528 ) 

529 outputs = self.grfn_vars_from_fullids( 

530 node.bot_interface_out.values() 

531 ) 

532 self.add_grfn_edges(inputs, bot_interface, outputs) 

533 

534 # add interface node and inputs to subraph 

535 subgraph.nodes.extend([bot_interface] + inputs) 

536 # add outputs to parent graph 

537 parent.nodes.extend(outputs) 

538 

539 self.subgraphs.add_node(subgraph) 

540 self.subgraphs.add_edge(parent, subgraph) 

541 

542 @_visit.register 

543 def visit_literal_value( 

544 self, node: AnnCastLiteralValue, subgraph: GrFNSubgraph 

545 ): 

546 pass 

547 

548 @_visit.register 

549 def visit_loop(self, node: AnnCastLoop, subgraph: GrFNSubgraph): 

550 if len(node.init) > 0: 

551 self.visit_node_list(node.init, subgraph) 

552 

553 parent = subgraph 

554 # make a new subgraph for this If Container 

555 type = "LoopContainer" 

556 border_color = GrFNSubgraph.get_border_color(type) 

557 metadata = create_container_metadata(node.grfn_con_src_ref) 

558 nodes = [] 

559 parent_str = parent.uid if parent is not None else None 

560 occs = 0 

561 uid = GenericNode.create_node_id() 

562 ns = "default-ns" 

563 scope = con_scope_to_str(node.con_scope) 

564 basename = "loop" # changed from 'scope' to the string 'loop' 

565 basename_id = int( 

566 node.con_scope[-1][4:] 

567 ) # stripping out the word 'loop' to obtain a numerical ID 

568 subgraph = GrFNLoopSubgraph( 

569 uid, 

570 ns, 

571 scope, 

572 basename, 

573 basename_id, 

574 occs, 

575 parent_str, 

576 type, 

577 border_color, 

578 nodes, 

579 metadata, 

580 ) 

581 self.subgraphs.add_node(subgraph) 

582 self.subgraphs.add_edge(parent, subgraph) 

583 

584 # build top interface 

585 if len(node.top_interface_initial) > 0: 

586 top_interface = self.create_loop_top_interface( 

587 node.top_interface_lambda 

588 ) 

589 self.network.add_node(top_interface, **top_interface.get_kwargs()) 

590 # collect initial GrFN VariableNodes 

591 grfn_initial = self.grfn_vars_from_fullids( 

592 node.top_interface_initial.values() 

593 ) 

594 # collect updated GrFN VariableNodes 

595 grfn_updated = self.grfn_vars_from_fullids( 

596 node.top_interface_updated.values() 

597 ) 

598 # combine initial and updated for inputs to loop top interface 

599 inputs = grfn_initial + grfn_updated 

600 # collect ouput GrFN VariableNodes 

601 outputs = self.grfn_vars_from_fullids( 

602 node.top_interface_out.values() 

603 ) 

604 self.add_grfn_edges(inputs, top_interface, outputs) 

605 

606 # add interface node, updated variables, and output variables to subgraph 

607 subgraph.nodes.extend([top_interface] + grfn_updated + outputs) 

608 

609 # visit expr, then setup condition info 

610 self.visit(node.expr, subgraph) 

611 self.create_condition_node( 

612 node.condition_in, 

613 node.condition_out, 

614 node.condition_lambda, 

615 subgraph, 

616 ) 

617 

618 self.visit_node_list(node.body, subgraph) 

619 

620 # build bot interface 

621 if len(node.bot_interface_in) > 0: 

622 bot_interface = self.create_interface_node( 

623 node.bot_interface_lambda 

624 ) 

625 self.network.add_node(bot_interface, **bot_interface.get_kwargs()) 

626 

627 inputs = self.grfn_vars_from_fullids( 

628 node.bot_interface_in.values() 

629 ) 

630 outputs = self.grfn_vars_from_fullids( 

631 node.bot_interface_out.values() 

632 ) 

633 self.add_grfn_edges(inputs, bot_interface, outputs) 

634 

635 # container includes input and bot interface 

636 # the outputs need to be added to the parent subgraph 

637 subgraph.nodes.extend(inputs + [bot_interface]) 

638 parent.nodes.extend(outputs) 

639 

640 @_visit.register 

641 def visit_model_break( 

642 self, node: AnnCastModelBreak, subgraph: GrFNSubgraph 

643 ): 

644 pass 

645 

646 @_visit.register 

647 def visit_model_continue( 

648 self, node: AnnCastModelContinue, subgraph: GrFNSubgraph 

649 ): 

650 pass 

651 

652 @_visit.register 

653 def visit_model_if(self, node: AnnCastModelIf, subgraph: GrFNSubgraph): 

654 parent = subgraph 

655 # make a new subgraph for this If Container 

656 type = "CondContainer" 

657 border_color = GrFNSubgraph.get_border_color(type) 

658 metadata = create_container_metadata(node.grfn_con_src_ref) 

659 nodes = [] 

660 parent_str = parent.uid if parent is not None else None 

661 occs = 0 

662 uid = GenericNode.create_node_id() 

663 ns = "default-ns" 

664 scope = con_scope_to_str(node.con_scope) 

665 basename = "if" # changed from 'scope' to the string 'if' 

666 basename_id = int( 

667 node.con_scope[-1][2:] 

668 ) # stripping out the word 'if' to obtain a numerical ID 

669 subgraph = GrFNSubgraph( 

670 uid, 

671 ns, 

672 scope, 

673 basename, 

674 basename_id, 

675 occs, 

676 parent_str, 

677 type, 

678 border_color, 

679 nodes, 

680 metadata, 

681 ) 

682 self.subgraphs.add_node(subgraph) 

683 self.subgraphs.add_edge(parent, subgraph) 

684 

685 # build top interface 

686 if len(node.top_interface_in) > 0: 

687 top_interface = self.create_interface_node( 

688 node.top_interface_lambda 

689 ) 

690 self.network.add_node(top_interface, **top_interface.get_kwargs()) 

691 

692 inputs = self.grfn_vars_from_fullids( 

693 node.top_interface_in.values() 

694 ) 

695 outputs = self.grfn_vars_from_fullids( 

696 node.top_interface_out.values() 

697 ) 

698 self.add_grfn_edges(inputs, top_interface, outputs) 

699 

700 # container includes top_interface and top_interface outputs 

701 subgraph.nodes.extend([top_interface] + outputs) 

702 

703 # visit expr, then setup condition info 

704 self.visit(node.expr, subgraph) 

705 self.create_condition_node( 

706 node.condition_in, 

707 node.condition_out, 

708 node.condition_lambda, 

709 subgraph, 

710 ) 

711 

712 self.visit_node_list(node.body, subgraph) 

713 self.visit_node_list(node.orelse, subgraph) 

714 

715 condition_var = node.condition_var 

716 if len(node.decision_in) > 0: 

717 self.create_decision_node( 

718 node.decision_in, 

719 node.decision_out, 

720 condition_var, 

721 node.decision_lambda, 

722 subgraph, 

723 ) 

724 

725 # build bot interface 

726 if len(node.bot_interface_in) > 0: 

727 bot_interface = self.create_interface_node( 

728 node.bot_interface_lambda 

729 ) 

730 self.network.add_node(bot_interface, **bot_interface.get_kwargs()) 

731 

732 inputs = self.grfn_vars_from_fullids( 

733 node.bot_interface_in.values() 

734 ) 

735 outputs = self.grfn_vars_from_fullids( 

736 node.bot_interface_out.values() 

737 ) 

738 self.add_grfn_edges(inputs, bot_interface, outputs) 

739 

740 # container includes input and bot interface 

741 # the outputs need to be added to the parent subgraph 

742 subgraph.nodes.extend(inputs + [bot_interface]) 

743 parent.nodes.extend(outputs) 

744 

745 @_visit.register 

746 def visit_model_return( 

747 self, node: AnnCastModelReturn, subgraph: GrFNSubgraph 

748 ): 

749 self.visit(node.value, subgraph) 

750 

751 self.visit_grfn_assignment(node.grfn_assignment, subgraph) 

752 

753 @_visit.register 

754 def visit_module(self, node: AnnCastModule, subgraph: GrFNSubgraph): 

755 type = "ModuleContainer" 

756 border_color = GrFNSubgraph.get_border_color(type) 

757 metadata = create_container_metadata(node.grfn_con_src_ref) 

758 nodes = [] 

759 occs = 0 

760 parent_str = None 

761 uid = GenericNode.create_node_id() 

762 ns = "default-ns" 

763 scope = MODULE_SCOPE 

764 basename = MODULE_SCOPE 

765 basename_id = -1 

766 subgraph = GrFNSubgraph( 

767 uid, 

768 ns, 

769 scope, 

770 basename, 

771 basename_id, 

772 occs, 

773 parent_str, 

774 type, 

775 border_color, 

776 nodes, 

777 metadata, 

778 ) 

779 self.subgraphs.add_node(subgraph) 

780 

781 self.visit_node_list(node.body, subgraph) 

782 

783 @_visit.register 

784 def visit_name(self, node: AnnCastName, subgraph: GrFNSubgraph): 

785 pass 

786 

787 @_visit.register 

788 def visit_set(self, node: AnnCastSet, subgraph: GrFNSubgraph): 

789 pass 

790 

791 @_visit.register 

792 def visit_tuple(self, node: AnnCastTuple, subgraph: GrFNSubgraph): 

793 self.visit_node_list(node.values, subgraph) 

794 

795 @_visit.register 

796 def visit_var(self, node: AnnCastVar, subgraph: GrFNSubgraph): 

797 self.visit(node.val, subgraph)