From 784782f3122a40e375e555c63d42e3c54f1b87e3 Mon Sep 17 00:00:00 2001 From: James Joseph Date: Thu, 25 Jun 2026 20:16:37 -0500 Subject: [PATCH] type selector APIs --- architecture_post_pr116_test.go | 2 +- architecture_prd103_test.go | 4 +- architecture_prd95_test.go | 4 +- architecture_remaining_test.go | 10 ++-- architecture_runtime_test.go | 2 +- bench_test.go | 14 ++--- benchmark_hotspots_test.go | 6 +-- conversion_plan.go | 28 +++++----- convert.go | 24 ++++----- convert_test.go | 94 ++++++++++++++++----------------- docs/api-mutation-audit.md | 8 +-- freqrestest.go | 24 ++++----- freqrestest_test.go | 4 +- pidtune.go | 20 +++---- pidtune_test.go | 24 ++++----- testmatlab_delay_test.go | 6 +-- 16 files changed, 137 insertions(+), 137 deletions(-) diff --git a/architecture_post_pr116_test.go b/architecture_post_pr116_test.go index 8e1f852..e8c9b19 100644 --- a/architecture_post_pr116_test.go +++ b/architecture_post_pr116_test.go @@ -35,7 +35,7 @@ func TestPostPR116CrossSeamConversionMetadataRationalAndFRD(t *testing.T) { t.Fatalf("discrete metadata InputName=%v OutputName=%v", disc.InputName, disc.OutputName) } - back, err := disc.D2C("zoh") + back, err := disc.D2C(C2DMethodZOH) if err != nil { t.Fatal(err) } diff --git a/architecture_prd103_test.go b/architecture_prd103_test.go index 9673470..629331d 100644 --- a/architecture_prd103_test.go +++ b/architecture_prd103_test.go @@ -108,7 +108,7 @@ func TestPRD103DelayConversionPolicyPublicWorkflows(t *testing.T) { t.Fatal(err) } - disc, err := cont.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := cont.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -200,7 +200,7 @@ func TestPRD103FrequencyAnalysisSampledResponseParity(t *testing.T) { if _, err := Margin(delayed); err != nil { t.Fatalf("delayed SISO Margin failed: %v", err) } - if _, err := Pidtune(delayed, "PI"); err != nil { + if _, err := Pidtune(delayed, PidtunePI); err != nil { t.Fatalf("delayed SISO Pidtune failed: %v", err) } } diff --git a/architecture_prd95_test.go b/architecture_prd95_test.go index 8384ef9..6f29ecc 100644 --- a/architecture_prd95_test.go +++ b/architecture_prd95_test.go @@ -52,7 +52,7 @@ func TestPRD95DelayBankPublicWorkflowsShareRules(t *testing.T) { t.Fatal(err) } - disc, err := plant.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := plant.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -279,7 +279,7 @@ func TestPRD95FrequencyResponseLayoutPublicWorkflowsAgree(t *testing.T) { u.Set(0, k, math.Sin(0.17*float64(k))) u.Set(1, k, math.Cos(0.11*float64(k))) } - disc, err := sys.D2D(dt, C2DOptions{Method: "tustin"}) + disc, err := sys.D2D(dt, C2DOptions{Method: C2DMethodTustin}) if err != nil { t.Fatal(err) } diff --git a/architecture_remaining_test.go b/architecture_remaining_test.go index 83f2a1b..f2d13c6 100644 --- a/architecture_remaining_test.go +++ b/architecture_remaining_test.go @@ -156,7 +156,7 @@ func TestRemainingArchitectureConversionPlannerBehavior(t *testing.T) { if err != nil { t.Fatal(err) } - discZOH, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh"}) + discZOH, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -169,7 +169,7 @@ func TestRemainingArchitectureConversionPlannerBehavior(t *testing.T) { t.Fatalf("InputDelay = %v, want [2 3]", discDefault.InputDelay) } - resampled, err := discDefault.D2D(0.05, C2DOptions{Method: "tustin"}) + resampled, err := discDefault.D2D(0.05, C2DOptions{Method: C2DMethodTustin}) if err != nil { t.Fatal(err) } @@ -180,10 +180,10 @@ func TestRemainingArchitectureConversionPlannerBehavior(t *testing.T) { t.Fatalf("state names = %v, want %v", resampled.StateName, sys.StateName) } - if _, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: "unknown"}); err == nil { + if _, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethod("unknown")}); err == nil { t.Fatal("expected unknown C2D method to fail") } - if _, err := discDefault.D2C("unknown"); err == nil { + if _, err := discDefault.D2C(C2DMethod("unknown")); err == nil { t.Fatal("expected unknown D2C method to fail") } } @@ -222,7 +222,7 @@ func TestRemainingArchitectureTimeDomainPolicyPublicConsumers(t *testing.T) { } } - contAgain, err := disc.D2C("tustin") + contAgain, err := disc.D2C(C2DMethodTustin) if err != nil { t.Fatal(err) } diff --git a/architecture_runtime_test.go b/architecture_runtime_test.go index b4f602c..786d6ba 100644 --- a/architecture_runtime_test.go +++ b/architecture_runtime_test.go @@ -49,7 +49,7 @@ func TestRuntimeArchitectureSISOLoopAnalysisRejectsMIMOConsistently(t *testing.T return err }}, {name: "Pidtune", run: func() error { - _, err := Pidtune(sys, "PI") + _, err := Pidtune(sys, PidtunePI) return err }}, } diff --git a/bench_test.go b/bench_test.go index 2b0657d..93b3ed4 100644 --- a/bench_test.go +++ b/bench_test.go @@ -474,7 +474,7 @@ func BenchmarkDiscretizeWithOpts_Thiran(b *testing.B) { sys.InputDelay = []float64{0.35} b.ResetTimer() for i := 0; i < b.N; i++ { - sys.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh", ThiranOrder: 3}) + sys.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) } } @@ -489,7 +489,7 @@ func BenchmarkDiscretizeWithOpts_IODelayThiran(b *testing.B) { sys.Delay = mat.NewDense(1, 1, []float64{0.35}) b.ResetTimer() for i := 0; i < b.N; i++ { - sys.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh", ThiranOrder: 3}) + sys.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) } } @@ -1364,7 +1364,7 @@ func BenchmarkD2C_ZOH_N2(b *testing.B) { sys := benchD2CSystem(2, 1, 0.05) b.ResetTimer() for i := 0; i < b.N; i++ { - sys.D2C("zoh") + sys.D2C(C2DMethodZOH) } } @@ -1372,7 +1372,7 @@ func BenchmarkD2C_ZOH_N5(b *testing.B) { sys := benchD2CSystem(5, 2, 0.05) b.ResetTimer() for i := 0; i < b.N; i++ { - sys.D2C("zoh") + sys.D2C(C2DMethodZOH) } } @@ -1380,7 +1380,7 @@ func BenchmarkD2C_ZOH_N20(b *testing.B) { sys := benchD2CSystem(20, 5, 0.01) b.ResetTimer() for i := 0; i < b.N; i++ { - sys.D2C("zoh") + sys.D2C(C2DMethodZOH) } } @@ -1388,7 +1388,7 @@ func BenchmarkD2C_ZOH_N50(b *testing.B) { sys := benchD2CSystem(50, 10, 0.01) b.ResetTimer() for i := 0; i < b.N; i++ { - sys.D2C("zoh") + sys.D2C(C2DMethodZOH) } } @@ -1396,7 +1396,7 @@ func BenchmarkD2C_Tustin_N20(b *testing.B) { sys := benchD2CSystem(20, 5, 0.01) b.ResetTimer() for i := 0; i < b.N; i++ { - sys.D2C("tustin") + sys.D2C(C2DMethodTustin) } } diff --git a/benchmark_hotspots_test.go b/benchmark_hotspots_test.go index ad325ce..00d0c4b 100644 --- a/benchmark_hotspots_test.go +++ b/benchmark_hotspots_test.go @@ -811,7 +811,7 @@ func BenchmarkPidtune_PI(b *testing.B) { sys := benchSysNonSym(4, 1, 1) b.ResetTimer() for i := 0; i < b.N; i++ { - Pidtune(sys, "PI") + Pidtune(sys, PidtunePI) } } @@ -819,7 +819,7 @@ func BenchmarkPidtune_PID(b *testing.B) { sys := benchSysNonSym(4, 1, 1) b.ResetTimer() for i := 0; i < b.N; i++ { - Pidtune(sys, "PID") + Pidtune(sys, PidtunePID) } } @@ -827,6 +827,6 @@ func BenchmarkPidtune_PIDF(b *testing.B) { sys := benchSysNonSym(4, 1, 1) b.ResetTimer() for i := 0; i < b.N; i++ { - Pidtune(sys, "PIDF") + Pidtune(sys, PidtunePIDF) } } diff --git a/conversion_plan.go b/conversion_plan.go index 5f5411c..7508c1b 100644 --- a/conversion_plan.go +++ b/conversion_plan.go @@ -9,8 +9,8 @@ type c2dPlan struct { sys *System dt float64 opts C2DOptions - method string - delayModeling string + method C2DMethod + delayModeling C2DDelayModeling contInputDelay []float64 contOutputDelay []float64 workSys *System @@ -26,10 +26,10 @@ func newC2DPlan(sys *System, dt float64, opts C2DOptions) (c2dPlan, error) { method := opts.Method if method == "" { - method = "zoh" + method = C2DMethodZOH } switch method { - case "zoh", "tustin", "foh", "impulse", "matched": + case C2DMethodZOH, C2DMethodTustin, C2DMethodFOH, C2DMethodImpulse, C2DMethodMatched: default: return c2dPlan{}, fmt.Errorf("DiscretizeWithOpts: unknown method %q", method) } @@ -37,10 +37,10 @@ func newC2DPlan(sys *System, dt float64, opts C2DOptions) (c2dPlan, error) { delayModeling := opts.DelayModeling if delayModeling == "" { - delayModeling = "state" + delayModeling = C2DDelayModelingState } switch delayModeling { - case "state", "internal": + case C2DDelayModelingState, C2DDelayModelingInternal: default: return c2dPlan{}, fmt.Errorf("DiscretizeWithOpts: unknown DelayModeling %q", delayModeling) } @@ -58,7 +58,7 @@ func newC2DPlan(sys *System, dt float64, opts C2DOptions) (c2dPlan, error) { } plan.workSys.InputDelay = nil plan.workSys.OutputDelay = nil - if sys.Delay != nil && (opts.ThiranOrder > 0 || delayModeling == "internal") { + if sys.Delay != nil && (opts.ThiranOrder > 0 || delayModeling == C2DDelayModelingInternal) { decomp := decomposedDelayMatrix(sys.Delay) if decomp.hasResidual() { plan.workSys.Delay = decomp.residual @@ -82,7 +82,7 @@ func (p c2dPlan) run() (*System, error) { return nil, err } - if p.delayModeling == "internal" { + if p.delayModeling == C2DDelayModelingInternal { return discretizeDelaysAsInternal(disc, p.contInputDelay, p.contOutputDelay, p.dt) } return p.applyExternalDelays(disc) @@ -120,18 +120,18 @@ func (p c2dPlan) applyExternalDelays(disc *System) (*System, error) { type d2cPlan struct { sys *System - method string + method C2DMethod } -func newD2CPlan(sys *System, method string) (d2cPlan, error) { +func newD2CPlan(sys *System, method C2DMethod) (d2cPlan, error) { if sys.IsContinuous() { return d2cPlan{}, fmt.Errorf("D2C: system already continuous: %w", ErrWrongDomain) } if method == "" { - method = "zoh" + method = C2DMethodZOH } switch method { - case "zoh", "tustin": + case C2DMethodZOH, C2DMethodTustin: return d2cPlan{sys: sys, method: method}, nil default: return d2cPlan{}, fmt.Errorf("D2C: unknown method %q (supported: \"tustin\", \"zoh\")", method) @@ -140,9 +140,9 @@ func newD2CPlan(sys *System, method string) (d2cPlan, error) { func (p d2cPlan) run() (*System, error) { switch p.method { - case "zoh": + case C2DMethodZOH: return p.sys.d2cZOH() - case "tustin": + case C2DMethodTustin: return p.sys.Undiscretize() default: panic("unvalidated D2C method") diff --git a/convert.go b/convert.go index 049ccf6..212052c 100644 --- a/convert.go +++ b/convert.go @@ -18,7 +18,7 @@ func (sys *System) Discretize(dt float64) (*System, error) { } if sys.HasInternalDelay() { - return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: "tustin"}) + return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: C2DMethodTustin}) } beta := 2.0 / dt @@ -147,9 +147,9 @@ func bilinear(sys *System, palpha, pbeta, alpha, beta float64) (*System, error) } type C2DOptions struct { - Method string + Method C2DMethod ThiranOrder int - DelayModeling string + DelayModeling C2DDelayModeling } type C2DMethod string @@ -404,7 +404,7 @@ func (sys *System) DiscretizeZOH(dt float64) (*System, error) { } if sys.HasInternalDelay() { - return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: "zoh"}) + return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: C2DMethodZOH}) } n, m, _ := sys.Dims() @@ -483,7 +483,7 @@ func discretizeWithInternalDelay(sys *System, dt float64, opts C2DOptions) (*Sys method := opts.Method if method == "" { - method = "zoh" + method = C2DMethodZOH } n, _, _ := sys.Dims() @@ -499,13 +499,13 @@ func discretizeWithInternalDelay(sys *System, dt float64, opts C2DOptions) (*Sys var Bd2 *mat.Dense switch method { - case "zoh": + case C2DMethodZOH: disc, Bd2, err = discretizeZOHAugmented(sys, dt) - case "tustin": + case C2DMethodTustin: disc, Bd2, err = discretizeTustinAugmented(sys, dt) - case "foh": + case C2DMethodFOH: disc, Bd2, err = discretizeFOHAugmented(sys, dt) - case "impulse": + case C2DMethodImpulse: disc, Bd2, err = discretizeImpulseAugmented(sys, dt) default: return nil, fmt.Errorf("DiscretizeWithOpts: unknown method %q", method) @@ -695,7 +695,7 @@ func (sys *System) DiscretizeImpulse(dt float64) (*System, error) { } if sys.HasInternalDelay() { - return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: "impulse"}) + return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: C2DMethodImpulse}) } n, m, _ := sys.Dims() @@ -788,7 +788,7 @@ func (sys *System) DiscretizeFOH(dt float64) (*System, error) { } if sys.HasInternalDelay() { - return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: "foh"}) + return discretizeWithInternalDelay(sys, dt, C2DOptions{Method: C2DMethodFOH}) } n, m, p := sys.Dims() @@ -1150,7 +1150,7 @@ func matchedGainFallback(num, den Poly, discZeros, discPoles []complex128, dt fl // axis and A_d − I non-singular (no eigenvalue exactly at 1). // // Delay fields are converted back to continuous (τ_c = τ_d · Dt). -func (sys *System) D2C(method string) (*System, error) { +func (sys *System) D2C(method C2DMethod) (*System, error) { plan, err := newD2CPlan(sys, method) if err != nil { return nil, err diff --git a/convert_test.go b/convert_test.go index 5ea5a0a..ee40b05 100644 --- a/convert_test.go +++ b/convert_test.go @@ -292,7 +292,7 @@ func TestDiscretizeWithOpts_ZOH_IntegerInputDelay(t *testing.T) { sys.InputDelay = []float64{0.3} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -315,7 +315,7 @@ func TestDiscretizeWithOpts_Tustin_IntegerOutputDelay(t *testing.T) { sys.OutputDelay = []float64{0.5} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "tustin"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodTustin}) if err != nil { t.Fatal(err) } @@ -334,7 +334,7 @@ func TestDiscretizeWithOpts_FractionalInputDelay_NoThiran_Error(t *testing.T) { ) sys.InputDelay = []float64{0.35} - _, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh"}) + _, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH}) if err == nil { t.Fatal("expected error for fractional delay without Thiran") } @@ -351,7 +351,7 @@ func TestDiscretizeWithOpts_FractionalInputDelay_WithThiran(t *testing.T) { sys.InputDelay = []float64{0.35} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -378,7 +378,7 @@ func TestDiscretizeWithOpts_FractionalOutputDelay_WithThiran(t *testing.T) { sys.OutputDelay = []float64{0.35} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -402,7 +402,7 @@ func TestDiscretizeWithOpts_MIMO_FractionalInputDelay_WithThiran(t *testing.T) { sys.InputDelay = []float64{0.35, 0.2} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -426,7 +426,7 @@ func TestDiscretizeWithOpts_MixedIntegerFractionalInputDelay(t *testing.T) { sys.InputDelay = []float64{0.3, 0.35} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -479,7 +479,7 @@ func TestDiscretizeWithOpts_FreqResponseWithThiran(t *testing.T) { sys.InputDelay = []float64{0.35} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -507,7 +507,7 @@ func TestDiscretizeWithOpts_ZeroDelay_NoThiranNeeded(t *testing.T) { sys.InputDelay = []float64{0} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -543,7 +543,7 @@ func TestDiscretizeWithOpts_SISO_FractionalIODelay_Thiran(t *testing.T) { sys.Delay = mat.NewDense(1, 1, []float64{0.35}) dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -574,7 +574,7 @@ func TestDiscretizeWithOpts_MIMO_DecomposableIODelay_Thiran(t *testing.T) { sys.Delay = mat.NewDense(2, 2, []float64{0.3, 0.5, 0.3, 0.5}) dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -595,7 +595,7 @@ func TestDiscretizeWithOpts_SISO_IntegerIODelay_NoThiran(t *testing.T) { sys.Delay = mat.NewDense(1, 1, []float64{0.3}) dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 0}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 0}) if err != nil { t.Fatal(err) } @@ -620,7 +620,7 @@ func TestDiscretizeWithOpts_MixedIODelay_InputDelay_Thiran(t *testing.T) { sys.InputDelay = []float64{0.15} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -748,7 +748,7 @@ func TestRoundtripDelays(t *testing.T) { sys.OutputDelay = []float64{0.5} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -865,7 +865,7 @@ func TestDiscretizeZOHWithInternalDelay(t *testing.T) { } dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -918,7 +918,7 @@ func TestDiscretizeZOHInternalDelayD22UpperTriangular(t *testing.T) { } dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -947,7 +947,7 @@ func TestDiscretizeZOHInternalDelayD22General(t *testing.T) { D22: mat.NewDense(2, 2, []float64{0.1, 0.5, 0.3, 0}), } - _, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: "zoh"}) + _, err := sys.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodZOH}) if err == nil { t.Fatal("expected error for non-upper-triangular D22") } @@ -969,7 +969,7 @@ func TestDiscretizeZOHInternalDelayFreqResp(t *testing.T) { } dt := 0.1 - disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -1000,7 +1000,7 @@ func TestC2DDelayModelingInternal(t *testing.T) { sys.InputDelay = []float64{0.35} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", DelayModeling: "internal"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, DelayModeling: C2DDelayModelingInternal}) if err != nil { t.Fatal(err) } @@ -1034,11 +1034,11 @@ func TestC2DDelayModelingInternalFreqResp(t *testing.T) { sys.InputDelay = []float64{0.35} dt := 0.1 - discInternal, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", DelayModeling: "internal"}) + discInternal, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, DelayModeling: C2DDelayModelingInternal}) if err != nil { t.Fatal(err) } - discState, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + discState, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -1074,7 +1074,7 @@ func TestC2DDelayModelingDefault(t *testing.T) { sys.InputDelay = []float64{0.3} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -1097,7 +1097,7 @@ func TestC2DDelayModelingIntegerDelay(t *testing.T) { sys.InputDelay = []float64{0.3} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", DelayModeling: "internal"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, DelayModeling: C2DDelayModelingInternal}) if err != nil { t.Fatal(err) } @@ -1120,7 +1120,7 @@ func TestC2DDelayModelingInternalOutputDelay(t *testing.T) { sys.OutputDelay = []float64{0.35} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", DelayModeling: "internal"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, DelayModeling: C2DDelayModelingInternal}) if err != nil { t.Fatal(err) } @@ -1151,7 +1151,7 @@ func TestC2DDelayModelingInternalMIMO(t *testing.T) { sys.InputDelay = []float64{0.35, 0.2} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", DelayModeling: "internal"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, DelayModeling: C2DDelayModelingInternal}) if err != nil { t.Fatal(err) } @@ -1338,7 +1338,7 @@ func TestImpulse_ViaOpts(t *testing.T) { if err != nil { t.Fatal(err) } - viaOpts, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "impulse"}) + viaOpts, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodImpulse}) if err != nil { t.Fatal(err) } @@ -1535,7 +1535,7 @@ func TestFOH_ViaOpts(t *testing.T) { if err != nil { t.Fatal(err) } - viaOpts, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "foh"}) + viaOpts, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodFOH}) if err != nil { t.Fatal(err) } @@ -1799,7 +1799,7 @@ func TestMatched_ViaOpts(t *testing.T) { if err != nil { t.Fatal(err) } - viaOpts, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "matched"}) + viaOpts, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodMatched}) if err != nil { t.Fatal(err) } @@ -1954,7 +1954,7 @@ func TestDiscretizeTustinWithInternalDelay(t *testing.T) { lft := makeLFTSystem(t) dt := 0.1 - disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: "tustin"}) + disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodTustin}) if err != nil { t.Fatal(err) } @@ -1984,7 +1984,7 @@ func TestDiscretizeFOHWithInternalDelay(t *testing.T) { lft := makeLFTSystem(t) dt := 0.1 - disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: "foh"}) + disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodFOH}) if err != nil { t.Fatal(err) } @@ -2009,7 +2009,7 @@ func TestDiscretizeImpulseWithInternalDelay(t *testing.T) { lft := makeLFTSystem(t) dt := 0.1 - disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: "impulse"}) + disc, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodImpulse}) if err != nil { t.Fatal(err) } @@ -2036,11 +2036,11 @@ func TestDiscretizeTustinAugmented_FreqResp(t *testing.T) { } dt := 0.1 - discZOH, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + discZOH, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } - discTustin, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: "tustin"}) + discTustin, err := lft.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodTustin}) if err != nil { t.Fatal(err) } @@ -2074,7 +2074,7 @@ func TestDiscretizeFOHAugmented_Stability(t *testing.T) { t.Fatal(err) } - disc, err := lft.DiscretizeWithOpts(0.1, C2DOptions{Method: "foh"}) + disc, err := lft.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodFOH}) if err != nil { t.Fatal(err) } @@ -2101,7 +2101,7 @@ func TestDiscretizeImpulseAugmented_SISO(t *testing.T) { t.Fatal(err) } - disc, err := lft.DiscretizeWithOpts(0.1, C2DOptions{Method: "impulse"}) + disc, err := lft.DiscretizeWithOpts(0.1, C2DOptions{Method: C2DMethodImpulse}) if err != nil { t.Fatal(err) } @@ -2116,10 +2116,10 @@ func TestDiscretizeImpulseAugmented_SISO(t *testing.T) { func TestD2C_WrongDomain(t *testing.T) { sys := makeTestSystem() - if _, err := sys.D2C("zoh"); err == nil { + if _, err := sys.D2C(C2DMethodZOH); err == nil { t.Fatal("expected ErrWrongDomain for continuous input") } - if _, err := sys.D2C("tustin"); err == nil { + if _, err := sys.D2C(C2DMethodTustin); err == nil { t.Fatal("expected ErrWrongDomain for continuous input") } } @@ -2129,7 +2129,7 @@ func TestD2C_UnknownMethod(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := disc.D2C("bogus"); err == nil { + if _, err := disc.D2C(C2DMethod("bogus")); err == nil { t.Fatal("expected error for unknown method") } } @@ -2142,7 +2142,7 @@ func TestD2C_Tustin_Roundtrip(t *testing.T) { if err != nil { t.Fatal(err) } - rec, err := disc.D2C("tustin") + rec, err := disc.D2C(C2DMethodTustin) if err != nil { t.Fatal(err) } @@ -2164,7 +2164,7 @@ func TestD2C_ZOH_Roundtrip(t *testing.T) { if err != nil { t.Fatal(err) } - rec, err := disc.D2C("zoh") + rec, err := disc.D2C(C2DMethodZOH) if err != nil { t.Fatal(err) } @@ -2185,11 +2185,11 @@ func TestD2C_ZOH_DefaultMethod(t *testing.T) { if err != nil { t.Fatal(err) } - recDefault, err := disc.D2C("") + recDefault, err := disc.D2C(C2DMethod("")) if err != nil { t.Fatal(err) } - recZOH, err := disc.D2C("zoh") + recZOH, err := disc.D2C(C2DMethodZOH) if err != nil { t.Fatal(err) } @@ -2204,7 +2204,7 @@ func TestD2C_ZOH_NegativeEigenvalue(t *testing.T) { D := mat.NewDense(1, 1, []float64{0}) sys, _ := New(A, B, C, D, 0.1) - _, err := sys.D2C("zoh") + _, err := sys.D2C(C2DMethodZOH) if err == nil { t.Fatal("expected ErrSingularTransform for negative-real eigenvalue") } @@ -2217,7 +2217,7 @@ func TestD2C_ZOH_EigenvalueAtOne(t *testing.T) { D := mat.NewDense(1, 1, []float64{0}) sys, _ := New(A, B, C, D, 0.1) - _, err := sys.D2C("zoh") + _, err := sys.D2C(C2DMethodZOH) if err == nil { t.Fatal("expected ErrSingularTransform for eigenvalue at 1 (A_d-I singular)") } @@ -2251,7 +2251,7 @@ func TestD2C_ZOH_MIMO_Roundtrip(t *testing.T) { if err != nil { t.Fatal(err) } - rec, err := disc.D2C("zoh") + rec, err := disc.D2C(C2DMethodZOH) if err != nil { t.Fatal(err) } @@ -2269,7 +2269,7 @@ func TestD2C_ZOH_N0_GainOnly(t *testing.T) { D: D, Dt: 0.1, } - rec, err := disc.D2C("zoh") + rec, err := disc.D2C(C2DMethodZOH) if err != nil { t.Fatal(err) } @@ -2298,7 +2298,7 @@ func TestD2C_InputDelayRoundtrip(t *testing.T) { if len(disc.InputDelay) != 1 || disc.InputDelay[0] != 2 { t.Fatalf("discrete input delay = %v, want [2]", disc.InputDelay) } - rec, err := disc.D2C("zoh") + rec, err := disc.D2C(C2DMethodZOH) if err != nil { t.Fatal(err) } diff --git a/docs/api-mutation-audit.md b/docs/api-mutation-audit.md index a2f2881..47f0db3 100644 --- a/docs/api-mutation-audit.md +++ b/docs/api-mutation-audit.md @@ -32,7 +32,7 @@ ownership clarity, and release readiness, not shrinking the toolbox shape. | P1 | `System`, `TransferFunc`, `ZPK`, `FRD`, result structs | Public fields make mutation possible by design, but ownership rules were not stated uniformly. | Fixed with package-level ownership docs for public mutable models, results, constructors, workspace-backed outputs, and nil receivers. | | P1 | `Lyap`, `DLyap`, `Care`, `Dare`, `Lqr`, `Dlqr`, `Lqi`, `Lqrd` | Workspace-backed result ownership was not consistently visible across Riccati/controller APIs. | Fixed by documenting workspace-backed result lifetime in package docs, `RiccatiOpts`, `RiccatiResult`, and `LyapunovOpts`. | | P1 | `PID`, `PID2`, `TransferFunc`, `ZPK`, `FRD`, `EKF` | Public model structs had no uniform copy helper outside `System`/array workflows. | Fixed with `Copy` methods and independence tests for each model type. | -| P2 | `D2C`, `Pidtune`, `FreqRespEst`, `C2DOptions` | Several APIs used free-form strings for method/type selectors. | Fixed with named constants for C2D/D2C methods, C2D delay modeling, PID tuning types, and frequency-response estimator methods while keeping string compatibility. | +| P2 | `D2C`, `Pidtune`, `FreqRespEst`, `C2DOptions` | Several APIs used free-form strings for method/type selectors. | Fixed with typed selector fields and parameters for C2D/D2C methods, C2D delay modeling, PID tuning types, and frequency-response estimator methods. | | P2 | `NewTrackingGoal`, `NewRejectionGoal`, `NewSensitivityGoal`, `NewWeightedGainGoal`, `NewLoopShapeGoal`, `NewMarginGoal`, `NewPoleGoal`, `NewOvershootGoal` | Convenience constructors call `mustTuningGoal` and can panic on invalid names or bounds. | Fixed by documenting panic behavior and pointing callers to error-returning `NewTuningGoal`. | | P2 | Nil receiver behavior | Some methods are nil-safe (`ModelArray`, `TunableReal` accessors), while most model methods are not. | Fixed with package-level docs: nil receivers are unsupported unless a method explicitly documents nil-safe behavior. | @@ -119,7 +119,7 @@ ownership clarity, and release readiness, not shrinking the toolbox shape. | `(*System).DiscretizeFOH` | `pure`, `returns-mutable` | First-order hold conversion. | | `(*System).DiscretizeMatched` | `pure`, `returns-mutable` | Matched pole-zero conversion. | | `(*System).Undiscretize` | `pure`, `returns-mutable` | Inverse bilinear conversion. | -| `(*System).D2C` | `pure`, `returns-mutable` | String method selector should stay documented. | +| `(*System).D2C` | `pure`, `returns-mutable` | Uses typed `C2DMethod` selector. | | `(*System).D2D` | `pure`, `returns-mutable` | Resamples through conversion policy. | ## Analysis, Synthesis, and Numerical Solvers @@ -203,7 +203,7 @@ ownership clarity, and release readiness, not shrinking the toolbox shape. | `(*System).Simulate` | `view-in`, `workspace`, `returns-mutable` | Mutates supplied buffers in `SimulateOpts`. | | `StepInfo` | `view-in`, `returns-mutable` | Reads response and returns metrics slice. | | `StepInfoForSystem` | `pure`, `returns-mutable` | Runs `Step` then `StepInfo`. | -| `FreqRespEst` | `view-in`, `returns-mutable` | String method selector remains compatibility risk. | +| `FreqRespEst` | `view-in`, `returns-mutable` | Uses typed `FreqRespEstMethod` selector through options. | | `(*FreqRespEstResult).CoherenceAt` | `pure` | Accessor over exported coherence slice. | | `(*FreqRespEstResult).FRD` | `returns-mutable` | Converts estimate to FRD. | | `ERA` | `view-in`, `returns-mutable` | Reads Markov matrices and returns identified model. | @@ -346,7 +346,7 @@ ownership clarity, and release readiness, not shrinking the toolbox shape. | `(*PID).System` | `returns-mutable` | Builds controller model. | | `(*PID2).Copy` | `copy-out`, `returns-mutable` | Copies 2-DOF controller scalar fields. | | `(*PID2).System` | `returns-mutable` | Builds 2-DOF controller model. | -| `Pidtune` | `view-in`, `returns-mutable` | String controller type selector. | +| `Pidtune` | `view-in`, `returns-mutable` | Uses typed `PidtuneType` selector. | | `NewEKF` | `copy-in`, `returns-mutable` | Copies `x0`, `P0`, `Q`, and `R`; stores model callback functions by reference. | | `(*EKF).Copy` | `copy-out`, `returns-mutable` | Deep-copies filter state, covariance, noise matrices, and work buffers. | | `(*EKF).Predict` | `mutates`, `alias-risk` | Mutates filter state/covariance; stores callback-returned state vector. | diff --git a/freqrestest.go b/freqrestest.go index 4edda4d..09aaecb 100644 --- a/freqrestest.go +++ b/freqrestest.go @@ -14,7 +14,7 @@ type FreqRespEstOpts struct { NFFT int Window func([]float64) []float64 NOverlap int - Method string // "h1" (default), "h2", "fft" + Method FreqRespEstMethod } type FreqRespEstMethod string @@ -38,7 +38,7 @@ type freqRespEstPlan struct { hop int nSeg int nFreq int - method string + method FreqRespEstMethod win []float64 winPower float64 } @@ -68,7 +68,7 @@ func FreqRespEst(input, output *mat.Dense, dt float64, opts *FreqRespEstOpts) (* return nil, err } - if plan.method == "fft" { + if plan.method == FreqRespEstFFT { return freqRespEstFFT(input, output, dt, m, p, N, plan.nfft, plan.win, plan.winPower) } @@ -88,23 +88,23 @@ func FreqRespEst(input, output *mat.Dense, dt float64, opts *FreqRespEstOpts) (* dt, m, p, plan.nfft, plan.nFreq, plan.hop, plan.nSeg, plan.winPower, plan.method) } -func validateFreqRespEstMethod(method string, m, p int) error { +func validateFreqRespEstMethod(method FreqRespEstMethod, m, p int) error { switch method { - case "h1", "h2", "fft": + case FreqRespEstH1, FreqRespEstH2, FreqRespEstFFT: default: return fmt.Errorf("FreqRespEst: unknown method %q: %w", method, ErrDimensionMismatch) } - if method == "h2" && (m != 1 || p != 1) { + if method == FreqRespEstH2 && (m != 1 || p != 1) { return fmt.Errorf("FreqRespEst: h2 estimator is only supported for SISO data: %w", ErrDimensionMismatch) } return nil } -func resolveOpts(opts *FreqRespEstOpts, N int) (nfft int, winFunc func([]float64) []float64, noverlap int, method string) { +func resolveOpts(opts *FreqRespEstOpts, N int) (nfft int, winFunc func([]float64) []float64, noverlap int, method FreqRespEstMethod) { nfft = 256 winFunc = window.Hann noverlap = -1 - method = "h1" + method = FreqRespEstH1 if opts != nil { if opts.NFFT > 0 { @@ -142,7 +142,7 @@ func newFreqRespEstPlan(opts *FreqRespEstOpts, N, m, p int) (freqRespEstPlan, er plan := freqRespEstPlan{nfft: nfft, noverlap: noverlap, method: method} plan.rebuildWindow(winFunc) - if method == "fft" { + if method == FreqRespEstFFT { return plan, nil } @@ -174,7 +174,7 @@ func (p *freqRespEstPlan) rebuildWindow(winFunc func([]float64) []float64) { func welchSISO(fft *fourier.FFT, seg []float64, _ []complex128, win []float64, uData []float64, _ int, yData []float64, _ int, - dt float64, nfft, nFreq, hop, nSeg int, _ float64, method string, + dt float64, nfft, nFreq, hop, nSeg int, _ float64, method FreqRespEstMethod, ) (*FreqRespEstResult, error) { Suu := make([]float64, nFreq) @@ -226,7 +226,7 @@ func welchSISO(fft *fourier.FFT, seg []float64, _ []complex128, win []float64, if Suu[f] < sthresh { continue } - if method == "h2" { + if method == FreqRespEstH2 { if cmplx.Abs(Syu[f]) < eps()*math.Sqrt(Syy[f]*Suu[f]) { continue } @@ -255,7 +255,7 @@ func welchSISO(fft *fourier.FFT, seg []float64, _ []complex128, win []float64, func welchMIMO(fft *fourier.FFT, seg []float64, _ []complex128, win []float64, input, output *mat.Dense, - dt float64, m, p, nfft, nFreq, hop, nSeg int, _ float64, _ string, + dt float64, m, p, nfft, nFreq, hop, nSeg int, _ float64, _ FreqRespEstMethod, ) (*FreqRespEstResult, error) { inR := input.RawMatrix() outR := output.RawMatrix() diff --git a/freqrestest_test.go b/freqrestest_test.go index 63621d8..9e1a1ad 100644 --- a/freqrestest_test.go +++ b/freqrestest_test.go @@ -191,7 +191,7 @@ func TestFreqRespEst_FFTMethod(t *testing.T) { u := mat.NewDense(1, N, uData) resp, _ := sys.Simulate(u, nil, nil) - est, err := FreqRespEst(u, resp.Y, dt, &FreqRespEstOpts{Method: "fft", NFFT: N}) + est, err := FreqRespEst(u, resp.Y, dt, &FreqRespEstOpts{Method: FreqRespEstFFT, NFFT: N}) if err != nil { t.Fatal(err) } @@ -318,7 +318,7 @@ func TestFreqRespEstRejectsUnknownMethod(t *testing.T) { u := mat.NewDense(1, 16, []float64{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}) y := mat.NewDense(1, 16, []float64{0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0}) - _, err := FreqRespEst(u, y, 0.1, &FreqRespEstOpts{Method: "bogus"}) + _, err := FreqRespEst(u, y, 0.1, &FreqRespEstOpts{Method: FreqRespEstMethod("bogus")}) if err == nil { t.Fatal("expected unknown method error") } diff --git a/pidtune.go b/pidtune.go index 548a3b9..5364ca5 100644 --- a/pidtune.go +++ b/pidtune.go @@ -23,7 +23,7 @@ const ( PidtunePIDF PidtuneType = "PIDF" ) -func Pidtune(plant *System, pidType string, opts ...PidtuneOptions) (*PID, error) { +func Pidtune(plant *System, pidType PidtuneType, opts ...PidtuneOptions) (*PID, error) { if _, err := newSISOLoopModel(plant, "pidtune"); err != nil { return nil, err } @@ -36,9 +36,9 @@ func Pidtune(plant *System, pidType string, opts ...PidtuneOptions) (*PID, error opt.PhaseMargin = 60 } - pidType = strings.ToUpper(pidType) + pidType = PidtuneType(strings.ToUpper(string(pidType))) switch pidType { - case "P", "I", "PI", "PD", "PID", "PIDF": + case PidtuneP, PidtuneI, PidtunePI, PidtunePD, PidtunePID, PidtunePIDF: default: return nil, fmt.Errorf("pidtune: unsupported type %q", pidType) } @@ -119,7 +119,7 @@ func evalPlantAt(plant *System, w float64) complex128 { return resp.At(0, 0, 0) } -func computePIDGains(plant *System, pidType string, wc, pmDeg float64) (*PID, error) { +func computePIDGains(plant *System, pidType PidtuneType, wc, pmDeg float64) (*PID, error) { h := evalPlantAt(plant, wc) magP := cmplx.Abs(h) phaseP := cmplx.Phase(h) * 180 / math.Pi @@ -140,22 +140,22 @@ func computePIDGains(plant *System, pidType string, wc, pmDeg float64) (*PID, er pid := &PID{} switch pidType { - case "P": + case PidtuneP: pid.Kp = 1.0 / magP - case "I": + case PidtuneI: pid.Ki = wc / magP - case "PI": + case PidtunePI: computePI(pid, wc, magP, phiC) - case "PD": + case PidtunePD: computePD(pid, wc, magP, phiC) - case "PID": + case PidtunePID: computePID(pid, wc, magP, phiC) - case "PIDF": + case PidtunePIDF: computePIDF(pid, wc, magP, phiC) } diff --git a/pidtune_test.go b/pidtune_test.go index 4d79b8c..8b30917 100644 --- a/pidtune_test.go +++ b/pidtune_test.go @@ -89,7 +89,7 @@ func makePlant(t *testing.T, num, den []float64) *System { func TestPidtune_PI_FirstOrder(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 1}) - pid, err := Pidtune(plant, "PI") + pid, err := Pidtune(plant, PidtunePI) if err != nil { t.Fatal(err) } @@ -102,7 +102,7 @@ func TestPidtune_PI_FirstOrder(t *testing.T) { func TestPidtune_PID_SecondOrder(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 2, 1}) - pid, err := Pidtune(plant, "PID") + pid, err := Pidtune(plant, PidtunePID) if err != nil { t.Fatal(err) } @@ -115,7 +115,7 @@ func TestPidtune_PID_SecondOrder(t *testing.T) { func TestPidtune_PI_Integrator(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 0}) - pid, err := Pidtune(plant, "PI") + pid, err := Pidtune(plant, PidtunePI) if err != nil { t.Fatal(err) } @@ -126,7 +126,7 @@ func TestPidtune_PI_Integrator(t *testing.T) { func TestPidtune_PI_Unstable(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, -1}) - pid, err := Pidtune(plant, "PI") + pid, err := Pidtune(plant, PidtunePI) if err != nil { t.Fatal(err) } @@ -137,7 +137,7 @@ func TestPidtune_PI_Unstable(t *testing.T) { func TestPidtune_PI_CustomCrossover(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 1}) - pid, err := Pidtune(plant, "PI", PidtuneOptions{CrossoverFrequency: 10}) + pid, err := Pidtune(plant, PidtunePI, PidtuneOptions{CrossoverFrequency: 10}) if err != nil { t.Fatal(err) } @@ -159,7 +159,7 @@ func TestPidtune_PI_CustomCrossover(t *testing.T) { func TestPidtune_P(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 1}) - pid, err := Pidtune(plant, "P") + pid, err := Pidtune(plant, PidtuneP) if err != nil { t.Fatal(err) } @@ -175,7 +175,7 @@ func TestPidtune_P(t *testing.T) { func TestPidtune_I(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 1}) - pid, err := Pidtune(plant, "I") + pid, err := Pidtune(plant, PidtuneI) if err != nil { t.Fatal(err) } @@ -191,7 +191,7 @@ func TestPidtune_I(t *testing.T) { func TestPidtune_PD(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 2, 1}) - pid, err := Pidtune(plant, "PD") + pid, err := Pidtune(plant, PidtunePD) if err != nil { t.Fatal(err) } @@ -205,7 +205,7 @@ func TestPidtune_PD(t *testing.T) { func TestPidtune_PIDF(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 2, 1}) - pid, err := Pidtune(plant, "PIDF") + pid, err := Pidtune(plant, PidtunePIDF) if err != nil { t.Fatal(err) } @@ -221,7 +221,7 @@ func TestPidtune_PIDF(t *testing.T) { func TestPidtune_InvalidType(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 1}) - _, err := Pidtune(plant, "FOO") + _, err := Pidtune(plant, PidtuneType("FOO")) if err == nil { t.Fatal("expected error for invalid type") } @@ -236,7 +236,7 @@ func TestPidtune_MIMO_Rejected(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = Pidtune(res.Sys, "PI") + _, err = Pidtune(res.Sys, PidtunePI) if err == nil { t.Fatal("expected error for MIMO plant") } @@ -244,7 +244,7 @@ func TestPidtune_MIMO_Rejected(t *testing.T) { func TestPidtune_CustomPM(t *testing.T) { plant := makePlant(t, []float64{1}, []float64{1, 1}) - pid, err := Pidtune(plant, "PI", PidtuneOptions{PhaseMargin: 45}) + pid, err := Pidtune(plant, PidtunePI, PidtuneOptions{PhaseMargin: 45}) if err != nil { t.Fatal(err) } diff --git a/testmatlab_delay_test.go b/testmatlab_delay_test.go index 17253ce..84044d8 100644 --- a/testmatlab_delay_test.go +++ b/testmatlab_delay_test.go @@ -224,7 +224,7 @@ func TestC2D_MATLAB_ZOH_IntegerDelay(t *testing.T) { sys.InputDelay = []float64{1.0} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh"}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH}) if err != nil { t.Fatal(err) } @@ -272,7 +272,7 @@ func TestC2D_MATLAB_ZOH_FractionalDelay(t *testing.T) { sys.InputDelay = []float64{0.25} dt := 0.1 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "zoh", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodZOH, ThiranOrder: 3}) if err != nil { t.Fatal(err) } @@ -301,7 +301,7 @@ func TestC2D_MATLAB_Tustin_Thiran(t *testing.T) { sys.InputDelay = []float64{2.7} dt := 1.0 - disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: "tustin", ThiranOrder: 3}) + disc, err := sys.DiscretizeWithOpts(dt, C2DOptions{Method: C2DMethodTustin, ThiranOrder: 3}) if err != nil { t.Fatal(err) }