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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func main() {
| `Series` | Cascade connection |
| `Parallel` | Sum connection |
| `Feedback` | Closed-loop with feedback |
| `SafeFeedback` | Feedback with automatic delay handling |
| `WithApproximatedDelays` / `WithPadeOrder` / `WithThiranOrder` | Feedback options for a delay-free rational closed loop |
| `Append` | Block diagonal concatenation |
| `SumBlk` | Sum block from string expression |
| `Connect` / `ConnectByName` | General interconnection by indices or signal names |
Expand Down
8 changes: 4 additions & 4 deletions architecture_prd103_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ func TestPRD103DelayConversionPolicyPublicWorkflows(t *testing.T) {
discPlant.InputDelay = []float64{2.5, 0}
discPlant.OutputDelay = []float64{0, 3.5}
discPlant.Delay = mat.NewDense(2, 2, []float64{0, 2.5, 3.5, 6.0})
if _, err := SafeFeedback(discPlant, controller, -1); !errors.Is(err, ErrFractionalDelay) {
t.Fatalf("SafeFeedback fractional delay error = %v, want ErrFractionalDelay", err)
if _, err := Feedback(discPlant, controller, -1, WithApproximatedDelays()); !errors.Is(err, ErrFractionalDelay) {
t.Fatalf("Feedback fractional delay error = %v, want ErrFractionalDelay", err)
}
closed, err := SafeFeedback(discPlant, controller, -1, WithThiranOrder(3))
closed, err := Feedback(discPlant, controller, -1, WithThiranOrder(3))
if err != nil {
t.Fatal(err)
}
if closed.HasDelay() {
t.Fatalf("SafeFeedback kept external delay: input=%v output=%v io=%v", closed.InputDelay, closed.OutputDelay, closed.Delay)
t.Fatalf("Feedback kept external delay: input=%v output=%v io=%v", closed.InputDelay, closed.OutputDelay, closed.Delay)
}
}

Expand Down
10 changes: 5 additions & 5 deletions architecture_prd95_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ func TestPRD95DelayBankPublicWorkflowsShareRules(t *testing.T) {
4.5, 8.0,
})

if _, err := SafeFeedback(discPlant, controller, -1); !errors.Is(err, ErrFractionalDelay) {
t.Fatalf("SafeFeedback without Thiran err = %v, want ErrFractionalDelay", err)
if _, err := Feedback(discPlant, controller, -1, WithApproximatedDelays()); !errors.Is(err, ErrFractionalDelay) {
t.Fatalf("Feedback without Thiran err = %v, want ErrFractionalDelay", err)
}
closed, err := SafeFeedback(discPlant, controller, -1, WithThiranOrder(3))
closed, err := Feedback(discPlant, controller, -1, WithThiranOrder(3))
if err != nil {
t.Fatal(err)
}
if closed.HasDelay() {
t.Fatalf("SafeFeedback kept external delay: input=%v output=%v io=%v", closed.InputDelay, closed.OutputDelay, closed.Delay)
t.Fatalf("Feedback kept external delay: input=%v output=%v io=%v", closed.InputDelay, closed.OutputDelay, closed.Delay)
}
}

Expand All @@ -106,7 +106,7 @@ func TestPRD95DelayBankKeepsIntegerDelayExactWithThiranOrder(t *testing.T) {
t.Fatal(err)
}

closed, err := SafeFeedback(sys, controller, -1, WithThiranOrder(3))
closed, err := Feedback(sys, controller, -1, WithThiranOrder(3))
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion architecture_remaining_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestRemainingArchitectureDelayTopologyPublicOperations(t *testing.T) {
if err != nil {
t.Fatal(err)
}
safeSplit, err := SafeFeedback(split, controller, -1, WithPadeOrder(2))
safeSplit, err := Feedback(split, controller, -1, WithPadeOrder(2))
if err != nil {
t.Fatal(err)
}
Expand Down
4 changes: 2 additions & 2 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,14 +540,14 @@ func BenchmarkDiscretizeWithOpts_IODelayThiran(b *testing.B) {
}
}

func BenchmarkSafeFeedback(b *testing.B) {
func BenchmarkFeedbackApproximatedDelays(b *testing.B) {
plant := benchSys(10, 3, 3)
plant.Dt = 1.0
ctrl := benchSys(5, 3, 3)
ctrl.Dt = 1.0
b.ResetTimer()
for i := 0; i < b.N; i++ {
SafeFeedback(plant, ctrl, -1)
Feedback(plant, ctrl, -1, WithApproximatedDelays())
}
}

Expand Down
11 changes: 10 additions & 1 deletion connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,13 @@ func sliceOrZeros(s []float64, n int) []float64 {
return make([]float64, n)
}

func Feedback(plant, controller *System, sign float64) (*System, error) {
// Feedback returns the closed-loop model of plant with controller in the
// feedback path. sign is -1 for negative feedback, +1 for positive; a nil
// controller closes unit feedback. The result is exact by default: delays
// are carried as internal delays when the loop topology supports them.
// Pass WithApproximatedDelays, WithPadeOrder, or WithThiranOrder to receive
// a delay-free rational model instead.
func Feedback(plant, controller *System, sign float64, opts ...FeedbackOption) (*System, error) {
if plant == nil {
return nil, fmt.Errorf("feedback: plant cannot be nil")
}
Expand All @@ -404,6 +410,9 @@ func Feedback(plant, controller *System, sign float64) (*System, error) {
if err := domainMatch(plant, controller); err != nil {
return nil, err
}
if cfg := newFeedbackConfig(opts); cfg.approximateDelays {
return feedbackWithApproximatedDelays(plant, controller, sign, cfg)
}
n1, m1, p1 := plant.Dims()
n2, m2, p2 := controller.Dims()
if p1 != m2 {
Expand Down
Loading
Loading