// Copyright (c) 2010, 2015 Kiel University and others.
// Copyright (c) 2022 Kiel University and others.
// Copyright (c) 2026 The elk-go Authors.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

// Package p1cycles implements ELK Layered phase 1.
package p1cycles import ( basegraph ) type GreedyCycleBreaker struct { ModelOrderTies bool } func () *GreedyCycleBreaker { return &GreedyCycleBreaker{} } func () *GreedyCycleBreaker { return &GreedyCycleBreaker{ModelOrderTies: true} } func (*GreedyCycleBreaker) ( *layered.LGraphArena, layered.LGraphID, ) layered.ProcessorConfiguration { return layered.GreedyCycleConfiguration() } func ( *GreedyCycleBreaker) ( *layered.LGraphArena, layered.LGraphID) error { := .Graph().LayerlessNodes := len() := make([]int, ) := make([]int, ) := make([]int, ) := make([]layered.LNodeID, 0) := make([]layered.LNodeID, 0) for , := range { .Node().ID = for , := range .Node().Ports { := .Port() for , := range .IncomingEdges { if .EdgeSourceNode() == { continue } := basegraph.Get(&.Edge().Properties, layered.PriorityDirection) if > 0 { [] += + 1 } else { []++ } } for , := range .OutgoingEdges { if .EdgeTargetNode() == { continue } := basegraph.Get(&.Edge().Properties, layered.PriorityDirection) if > 0 { [] += + 1 } else { []++ } } } if [] == 0 { = append(, ) } else if [] == 0 { = append(, ) } } := func( layered.LNodeID) { for , := range .Node().Ports { for , := range .PortConnectedEdges() { := .EdgeOtherPort(, ) := *.Port().Node if == { continue } := basegraph.Get(&.Edge().Properties, layered.PriorityDirection) if < 0 { = 0 } := .Node().ID if [] != 0 { continue } if .Edge().Target != nil && *.Edge().Target == { [] -= + 1 if [] <= 0 && [] > 0 { = append(, ) } } else { [] -= + 1 if [] <= 0 && [] > 0 { = append(, ) } } } } } := , := -1, 1 for > 0 { for len() > 0 { := [0] = [1:] := .Node().ID if [] != 0 { continue } [] = -- () -- } for len() > 0 { := [0] = [1:] := .Node().ID if [] != 0 { continue } [] = ++ () -- } if == 0 { break } := math.MinInt := make([]layered.LNodeID, 0) for , := range { := .Node().ID if [] != 0 { continue } := [] - [] if >= { if > { = [:0] = } = append(, ) } } if len() == 0 { return fmt.Errorf("layered p1: no unprocessed maximum-outflow node") } := .chooseMaximumNode(, , ) [.Node().ID] = ++ () -- } := + 1 for := range { if [] < 0 { [] += } } for , := range { for , := range append([]layered.LPortID(nil), .Node().Ports...) { for , := range append([]layered.LEdgeID(nil), .Port().OutgoingEdges...) { := .EdgeTargetNode() if [.Node().ID] > [.Node().ID] { .ReverseEdge(, , true) basegraph.Set(&.Graph().Properties, layered.Cyclic, true) } } } } return nil } func ( *GreedyCycleBreaker) ( *layered.LGraphArena, layered.LGraphID, []layered.LNodeID, ) layered.LNodeID { if .ModelOrderTies { := math.MaxInt var layered.LNodeID := false for , := range { , := basegraph.TryGet(&.Node().Properties, layered.ModelOrder) if && < { , , = , , true } } if { return } } , := basegraph.TryGet(&.Graph().Properties, layered.Random) if ! || == nil { = javacompatRandom(, ) } return [.NextIntN(int32(len()))] } func javacompatRandom( *layered.LGraphArena, layered.LGraphID) *javacompat.Random { layered.ConfigureGraphProperties(, ) return basegraph.Get(&.Graph().Properties, layered.Random) }