/// <summary> /// Analog Electronic Filters pp.58 /// </summary> private void DrawFirstOrderFilter(int nStage, FirstOrderComplexRationalPolynomial pf, double resistorValue) { /* a == 1/R0C0 * C0 = R0/a */ double r0 = 1; double c0 = r0 / pf.D(0).real; // 周波数スケーリング。キャパシタの値をωcで割る。 double ωc = CutoffFrequencyHz * 2.0 * Math.PI; c0 /= ωc; // 最後に抵抗値を全て10 * 1000倍、キャパシターの容量を10*1000分の1にする。 r0 *= resistorValue; c0 /= resistorValue; textBoxParameters.Text += string.Format("Stage {0} is 1st degree Passive LPF. Fc={1}Hz\n", nStage + 1, ValueString(CutoffFrequencyHz)); // 抵抗R0 AddResistorH(mX, CIRCUIT_INPUT_LINE_H, ResistorValueString(r0)); // 抵抗の右から出る横線。右にアンプがある。 AddLine(mX + RESISTOR_LENGTH, CIRCUIT_INPUT_LINE_H, mX + RESISTOR_LENGTH + 30, CIRCUIT_INPUT_LINE_H, mBrush); mX += RESISTOR_LENGTH + 10; // 抵抗R0とキャパシタC0をつなげる縦線。 AddLine(mX, CIRCUIT_INPUT_LINE_H, mX, CIRCUIT_INPUT_LINE_H + 30, mBrush); // キャパシターC0 AddCapacitorV(mX, CIRCUIT_INPUT_LINE_H + 30, CapacitorValueString(c0)); // キャパシタC0からGNDに接続する線。 AddLine(mX, CIRCUIT_INPUT_LINE_H + 30 + CAPACITOR_THICKNESS, mX, CIRCUIT_INPUT_LINE_H + 30 + CAPACITOR_THICKNESS + RESISTOR_LENGTH + 10, mBrush); // GND AddGnd(mX, CIRCUIT_INPUT_LINE_H + 30 + CAPACITOR_THICKNESS + RESISTOR_LENGTH + 10); mX += 20; // ボルテージフォロアー。 AddVoltageFollower(); // オペアンプの出力線。 AddLine(mX, CIRCUIT_INPUT_LINE_H, mX + 50, CIRCUIT_INPUT_LINE_H, mBrush); mX += 50; }
/// <summary> /// 連続時間フィルターの伝達関数を離散時間フィルターの伝達関数にBilinear transformする。 /// Discrete-time signal processing 3rd ed. pp.533 /// Benoit Boulet, 信号処理とシステムの基礎 pp.681-682 /// 三谷政昭, ディジタル・フィルタ理論&設計入門 pp.193 /// </summary> /// <param name="ps">連続時間フィルターの伝達関数</param> /// <returns>離散時間フィルターの伝達関数</returns> public FirstOrderComplexRationalPolynomial StoZ(FirstOrderComplexRationalPolynomial ps) { /* * n1s + n0 * ps = ────────── * d1s + d0 * * z^{-1} = zM とする。 * * Bilinear transform: * 2 1-zM * s → ─── * ────── * Td 1+zM * * 2/Td = kとすると以下のように書ける。 * * k(1-zM) * s → ─────── * 1+zM * * k(1-zM) n1*k(1-zM) + n0(1+zM) * n1 * ─────── + n0 ───────────────────── * 1+zM 1+zM n1*k(1-zM) + n0(1+zM) (n0-n1*k)zM + n0+n1*k * pz = ─────────────────── = ──────────────────────── = ───────────────────── = ───────────────────── * k(1-zM) d1*k(1-zM) + d0(1+zM) d1*k(1-zM) + d0(1+zM) (d0-d1*k)zM + d0+d1*k * d1 * ─────── + d0 ───────────────────── * 1+zM 1+zM * */ // 都合により、投入されるアナログフィルターの伝達関数psは、 // s' == s / ωcとしたs'についての式であることに注意!! double twoπ = 2.0 * Math.PI; double ωc = PrewarpωtoΩ(mMatchFreq * twoπ); double k = (1.0 / ωc) * (2.0 * mSampleFreq); var n0 = ps.N(0); var n1k = WWComplex.Mul(ps.N(1), k); var d0 = ps.D(0); var d1k = WWComplex.Mul(ps.D(1), k); var pz = new FirstOrderComplexRationalPolynomial( WWComplex.Sub(n0, n1k), WWComplex.Add(n0, n1k), WWComplex.Sub(d0, d1k), WWComplex.Add(d0, d1k)); return(pz); }
public static FirstOrderComplexRationalPolynomial LowpassToHighpass(FirstOrderComplexRationalPolynomial p) { /* * z^-1 → -z^-1に置き換える。 * * n1 z^-1 + n0 -n1 z^-1 + n0 * ────────────── → ─────────────── * d1 z^-1 + d0 -d1 z^-1 + d0 * */ var r = new FirstOrderComplexRationalPolynomial( p.N(1).Minus(), p.N(0), p.D(1).Minus(), p.D(0)); return(r); }
private static WWComplex InverseLaplaceTransformOne(FirstOrderComplexRationalPolynomial p, double t) { if (t < 0) { return(WWComplex.Zero()); } if (!p.N(1).EqualValue(WWComplex.Zero())) { // 約分によって分子が定数になるはずである。 throw new NotImplementedException(); } if (p.D(1).EqualValue(WWComplex.Zero()) && p.D(0).EqualValue(WWComplex.Unity())) { // 定数。 // 1 → δ(t) if (t == 0) { return(p.N(0)); } return(WWComplex.Zero()); } if (p.D(0).EqualValue(WWComplex.Zero())) { System.Diagnostics.Debug.Assert(!p.D(1).EqualValue(WWComplex.Zero())); // b/s → b*u(t) return(p.N(0)); } // b/(s-a) → b * exp(a * t) return(WWComplex.Mul(p.N(0), new WWComplex(Math.Exp(-t * p.D(0).real) * Math.Cos(-t * p.D(0).imaginary), Math.Exp(-t * p.D(0).real) * Math.Sin(-t * p.D(0).imaginary)))); }
/// <summary> /// ローパスフィルターの設計。 /// </summary> /// <param name="mG0">0Hzのゲイン (dB)</param> /// <param name="mGc">カットオフ周波数のゲイン (dB)</param> /// <param name="mGs">ストップバンドのゲイン (dB)</param> /// <param name="mFc">カットオフ周波数(Hz)</param> /// <param name="mFs">ストップバンドの下限周波数(Hz)</param> /// <returns></returns> public bool DesignLowpass(double g0, double gc, double gs, double fc, double fs, FilterType ft, ApproximationBase.BetaType betaType) { // Hz → rad/s double ωc = fc * 2.0 * Math.PI; double ωs = fs * 2.0 * Math.PI; // dB → linear double h0 = Math.Pow(10, g0 / 20); double hc = Math.Pow(10, gc / 20); double hs = Math.Pow(10, gs / 20); ApproximationBase filter; switch (ft) { case FilterType.Butterworth: filter = new ButterworthDesign(h0, hc, hs, ωc, ωs, betaType); break; case FilterType.Chebyshev: filter = new ChebyshevDesign(h0, hc, hs, ωc, ωs, betaType); break; case FilterType.Pascal: filter = new PascalDesign(h0, hc, hs, ωc, ωs, betaType); break; case FilterType.InverseChebyshev: filter = new InverseChebyshevDesign(h0, hc, hs, ωc, ωs, betaType); break; case FilterType.Cauer: filter = new CauerDesign(h0, hc, hs, ωc, ωs, betaType); break; default: throw new NotImplementedException(); } mOrder = filter.Order; mNumeratorConstant = filter.TransferFunctionConstant(); // 伝達関数のポールの位置。 mPoleList.Clear(); for (int i = 0; i < filter.NumOfPoles(); ++i) { mPoleList.Add(filter.PoleNth(i)); } // 伝達関数の零の位置。 mZeroList.Clear(); for (int i = 0; i < filter.NumOfZeroes(); ++i) { mZeroList.Add(filter.ZeroNth(i)); } TransferFunction = (WWComplex s) => { return(TransferFunctionValue(WWComplex.Div(s, ωc))); }; PoleZeroPlotTransferFunction = (WWComplex s) => { return(TransferFunctionValue(s)); }; { // Unit Step Function WWPolynomial.PolynomialAndRationalPolynomial H_s; { var unitStepRoots = new WWComplex[filter.NumOfPoles() + 1]; for (int i = 0; i < filter.NumOfPoles(); ++i) { var p = filter.PoleNth(i); unitStepRoots[i] = p; } unitStepRoots[unitStepRoots.Length - 1] = WWComplex.Zero(); var numerCoeffs = new List <WWComplex>(); if (filter.NumOfZeroes() == 0) { numerCoeffs.Add(new WWComplex(mNumeratorConstant, 0)); } else { numerCoeffs.AddRange(WWPolynomial.RootListToCoeffList(mZeroList.ToArray(), new WWComplex(mNumeratorConstant, 0))); } H_s = WWPolynomial.Reduction(numerCoeffs.ToArray(), unitStepRoots); } var H_fraction = WWPolynomial.PartialFractionDecomposition(H_s.numerCoeffList, H_s.denomRootList); var H_integer = FirstOrderComplexRationalPolynomial.CreateFromCoeffList(H_s.coeffList); UnitStepResponseFunction = (double t) => { return(InverseLaplaceTransformValue(H_fraction, H_integer, t)); }; } { // 伝達関数 Transfer function WWPolynomial.PolynomialAndRationalPolynomial H_s; { var H_Roots = new WWComplex[filter.NumOfPoles()]; for (int i = 0; i < filter.NumOfPoles(); ++i) { var p = filter.PoleNth(i); H_Roots[i] = p; } var numerCoeffs = new List <WWComplex>(); if (filter.NumOfZeroes() == 0) { numerCoeffs.Add(new WWComplex(mNumeratorConstant, 0)); } else { numerCoeffs.AddRange(WWPolynomial.RootListToCoeffList(mZeroList.ToArray(), new WWComplex(mNumeratorConstant, 0))); } H_s = WWPolynomial.Reduction(numerCoeffs.ToArray(), H_Roots); } var H_fraction = WWPolynomial.PartialFractionDecomposition(H_s.numerCoeffList, H_s.denomRootList); var H_integer = FirstOrderComplexRationalPolynomial.CreateFromCoeffList(H_s.coeffList); ImpulseResponseFunction = (double t) => { return(InverseLaplaceTransformValue(H_fraction, H_integer, t)); }; #if true mH_PFD = new List <FirstOrderComplexRationalPolynomial>(); for (int i = 0; i < H_fraction.Count; ++i) { mH_PFD.Add(H_fraction[i]); if (1 == H_integer.Count && i == H_fraction.Count / 2 - 1) { mH_PFD.Add(H_integer[0]); } } #else mH_PFD = FirstOrderComplexRationalPolynomial.Add(H_fraction, H_integer); #endif TimeDomainFunctionTimeScale = 1.0 / filter.CutoffFrequencyHz(); if (NumOfZeroes() == 0) { // 共役複素数のペアを組み合わせて伝達関数の係数を全て実数にする。 // s平面のjω軸から遠い項から並べる。 mRealPolynomialList.Clear(); if ((H_fraction.Count() & 1) == 1) { // 奇数。 int center = H_fraction.Count() / 2; mRealPolynomialList.Add(H_fraction[center]); for (int i = 0; i < H_fraction.Count() / 2; ++i) { mRealPolynomialList.Add(WWPolynomial.Mul( H_fraction[center - i - 1], H_fraction[center + i + 1])); } } else { // 偶数。 int center = H_fraction.Count() / 2; for (int i = 0; i < H_fraction.Count() / 2; ++i) { mRealPolynomialList.Add(WWPolynomial.Mul( H_fraction[center - i - 1], H_fraction[center + i])); } } #if true System.Diagnostics.Debug.Assert(H_integer.Count == 0); #else { // integral polynomialは全て実数係数の多項式。単に足す。 foreach (var p in H_integer) { mRealPolynomialList.Add(p); } } #endif } else { // 共役複素数のペアを組み合わせて伝達関数の係数を全て実数にする。 // s平面のjω軸から遠い項から並べる。 // zeroListとPoleListを使って、実係数の2次多項式を作り、 // 伝達関数をこういう感じに実係数2次有理多項式の積の形にする。 // (s^2+c1)(s^2+c2) (s^2+c1) (s^2+c2) // H(s) = ──────────────────────── ⇒ ──────────── * ──────────── // (s^2+a1s+b1)(s^2+a2s+b2) (s^2+a1s+b1) (s^2+a2s+b2) mRealPolynomialList.Clear(); if ((mPoleList.Count & 1) == 1) { // 奇数。 int center = mPoleList.Count / 2; mRealPolynomialList.Add(new FirstOrderComplexRationalPolynomial( WWComplex.Zero(), WWComplex.Unity(), WWComplex.Unity(), WWComplex.Minus(mPoleList[center]))); for (int i = 0; i < mPoleList.Count / 2; ++i) { var p0 = new FirstOrderComplexRationalPolynomial( WWComplex.Unity(), WWComplex.Minus(mZeroList[center - i - 1]), WWComplex.Unity(), WWComplex.Minus(mPoleList[center - i - 1])); var p1 = new FirstOrderComplexRationalPolynomial( WWComplex.Unity(), WWComplex.Minus(mZeroList[center + i]), WWComplex.Unity(), WWComplex.Minus(mPoleList[center + i + 1])); var p = WWPolynomial.Mul(p0, p1); mRealPolynomialList.Add(p); } } else { // 偶数。 int center = mPoleList.Count / 2; for (int i = 0; i < mPoleList.Count / 2; ++i) { var p0 = new FirstOrderComplexRationalPolynomial( WWComplex.Unity(), WWComplex.Minus(mZeroList[center - i - 1]), WWComplex.Unity(), WWComplex.Minus(mPoleList[center - i - 1])); var p1 = new FirstOrderComplexRationalPolynomial( WWComplex.Unity(), WWComplex.Minus(mZeroList[center + i]), WWComplex.Unity(), WWComplex.Minus(mPoleList[center + i])); var p = WWPolynomial.Mul(p0, p1); mRealPolynomialList.Add(p); } } } return(true); } }
/// <summary> /// Design of Discrete-time IIR filters from continuous-time filter using impulse invariance method /// A. V. Oppenheim, R. W. Schafer, Discrete-Time Signal Processing, 3rd Ed, Prentice Hall, 2009 /// pp. 526 - 529 /// /// minimumPhase==true : 多項式の積の形の伝達関数が出てくる。 /// minimumPhase==false : mixed phaseとなり、多項式の和の形の伝達関数が出てくる。 /// </summary> public ImpulseInvarianceMethod(List <FirstOrderComplexRationalPolynomial> H_s, double ωc, double sampleFreq, bool minimumPhase) { mMinimumPhase = minimumPhase; mSamplingFrequency = sampleFreq; /* * H_sはノーマライズされているので、戻す。 * * b b * ωc * ────────── = ──────────── * s/ωc - a s - a * ωc */ double td = 1.0 / sampleFreq; mComplexHzList.Clear(); foreach (var pS in H_s) { WWComplex sktd; if (pS.DenomDegree() == 0) { System.Diagnostics.Debug.Assert(pS.D(0).EqualValue(WWComplex.Unity())); // ? // a * u[t] → exp^(a) sktd = WWComplex.Minus(WWComplex.Mul(WWComplex.Unity(), ωc * td)); } else { sktd = WWComplex.Minus(WWComplex.Mul(pS.D(0), ωc * td)); } // e^{sktd} = e^{real(sktd)} * e^{imag{sktd}} // = e^{real(sktd)} * ( cos(imag{sktd}) + i*sin(imag{sktd}) var expsktd = new WWComplex( Math.Exp(sktd.real) * Math.Cos(sktd.imaginary), Math.Exp(sktd.real) * Math.Sin(sktd.imaginary)); // pZは、z^-1についての式。 // y[1] : z^{-1}の項 // y[0] : 定数項 var pZ = new FirstOrderComplexRationalPolynomial( WWComplex.Zero(), WWComplex.Mul(pS.N(0), ωc * td), WWComplex.Minus(expsktd), WWComplex.Unity()); mComplexHzList.Add(pZ); } mH_z = new HighOrderComplexRationalPolynomial(mComplexHzList[0]); for (int i = 1; i < mComplexHzList.Count; ++i) { mH_z = WWPolynomial.Add(mH_z, mComplexHzList[i]); } if (mMinimumPhase) { // ミニマムフェーズにする。 var numerPoly = mH_z.NumerPolynomial(); var aCoeffs = new double[numerPoly.Degree + 1]; for (int i = 0; i < aCoeffs.Length; ++i) { aCoeffs[i] = numerPoly.C(i).real; } var rpoly = new JenkinsTraubRpoly(); bool result = rpoly.FindRoots(new RealPolynomial(aCoeffs)); if (!result) { Console.WriteLine("Error: rpoly.FindRoots failed!"); throw new ArgumentException(); } // ポールの位置 = mHzListの多項式の分母のリストから判明する。 var poles = new WWComplex[mComplexHzList.Count]; for (int i = 0; i < mComplexHzList.Count; ++i) { var p = mComplexHzList[i]; Console.WriteLine(" {0} {1}", i, p); poles[i] = WWComplex.Div(p.D(0), p.D(1)).Minus(); } System.Diagnostics.Debug.Assert(poles.Length == rpoly.NumOfRoots() + 1); var zeroes = new WWComplex[rpoly.NumOfRoots()]; for (int i = 0; i < rpoly.NumOfComplexRoots() / 2; ++i) { var p0 = rpoly.ComplexRoot(i * 2); var p1 = rpoly.ComplexRoot(i * 2 + 1); if (p0.Magnitude() < 1.0) { // 単位円の外側にゼロがあるのでconjugate reciprocalにする。 p0 = p0.ConjugateReciprocal(); p1 = p1.ConjugateReciprocal(); } zeroes[i * 2] = p0; zeroes[i * 2 + 1] = p1; } for (int i = 0; i < rpoly.NumOfRealRoots(); ++i) { var p = rpoly.RealRoot(i); if (p.Magnitude() < 1.0) { // 単位円の外側にゼロがあるのでconjugate reciprocalにする。 p = p.ConjugateReciprocal(); } zeroes[i + rpoly.NumOfComplexRoots()] = p; } mComplexHzList.Clear(); // ポールと零のペアを、係数を実数化出来るように対称に並べる。 // ポールと共役のペアになっていて、ペアは例えばn=5の時 // C0+ C1+ R C1- C0- のように並んでいる。 // 零は共役のペアになっていて、ペアは例えばn=5の時 // C0+ C0- C1+ C1- R のように並んでいる。 // mComplexHzListは C0+ C0- C1+ C1- R のように並べる。 for (int i = 0; i < poles.Length / 2; ++i) { var cP = new FirstOrderComplexRationalPolynomial( WWComplex.Unity(), zeroes[i * 2].Minus(), WWComplex.Unity(), poles[i].Minus()); mComplexHzList.Add(cP); var cM = new FirstOrderComplexRationalPolynomial( WWComplex.Unity(), zeroes[i * 2 + 1].Minus(), WWComplex.Unity(), poles[poles.Length - 1 - i].Minus()); mComplexHzList.Add(cM); } { var p = new FirstOrderComplexRationalPolynomial( WWComplex.Zero(), WWComplex.Unity(), WWComplex.Unity(), poles[poles.Length / 2].Minus()); mComplexHzList.Add(p); } // 0Hz (z^-1 == 1)のときのゲインが1になるようにする。 WWComplex gain = WWComplex.Unity(); foreach (var p in mComplexHzList) { gain = WWComplex.Mul(gain, p.Evaluate(WWComplex.Unity())); } mComplexHzList[mComplexHzList.Count - 1] = mComplexHzList[mComplexHzList.Count - 1].ScaleNumeratorCoeffs(1.0 / gain.real); var gainC = WWComplex.Unity(); foreach (var p in mComplexHzList) { gainC = WWComplex.Mul(gainC, p.Evaluate(WWComplex.Unity())); } // 係数が全て実数のmRealHzListを作成する。 mRealHzList.Clear(); for (int i = 0; i < mComplexHzList.Count / 2; ++i) { var p0 = mComplexHzList[i * 2]; var p1 = mComplexHzList[i * 2 + 1]; var p = WWPolynomial.Mul(p0, p1).ToRealPolynomial(); mRealHzList.Add(p); } mRealHzList.Add(new RealRationalPolynomial( new double[] { 1.0 / gain.real }, new double[] { -poles[poles.Length / 2].real, 1.0 })); var gainR = 1.0; foreach (var p in mRealHzList) { gainR *= p.Evaluate(1.0); } // mH_zを作り直す。 var newNumerCoeffs = WWPolynomial.RootListToCoeffList(zeroes, WWComplex.Unity()); var poleCoeffs = mH_z.DenomPolynomial().ToArray(); mH_z = new HighOrderComplexRationalPolynomial(newNumerCoeffs, poleCoeffs); var gain2 = mH_z.Evaluate(WWComplex.Unity()); for (int i = 0; i < newNumerCoeffs.Length; ++i) { newNumerCoeffs[i] = WWComplex.Mul(newNumerCoeffs[i], 1.0 / gain2.Magnitude()); } mH_z = new HighOrderComplexRationalPolynomial(newNumerCoeffs, poleCoeffs); var gain3 = mH_z.Evaluate(WWComplex.Unity()); Console.WriteLine(mH_z.ToString("z", WWMathUtil.SymbolOrder.Inverted)); } else { // mixed-phase // mComplexHzListは多項式の和の形になっている。 // 0Hz (z^-1 == 1)のときのゲインが1になるようにする。 WWComplex gain = WWComplex.Zero(); foreach (var p in mComplexHzList) { gain = WWComplex.Add(gain, p.Evaluate(WWComplex.Unity())); } mComplexHzList[mComplexHzList.Count / 2] = mComplexHzList[mComplexHzList.Count / 2].ScaleNumeratorCoeffs(1.0 / gain.real); var gainC = WWComplex.Zero(); foreach (var p in mComplexHzList) { gainC = WWComplex.Add(gainC, p.Evaluate(WWComplex.Unity())); } // 係数が全て実数のmRealHzListを作成する。 // mRealHzListは、多項式の和を表現する。 mRealHzList.Clear(); for (int i = 0; i < mComplexHzList.Count / 2; ++i) { var p0 = mComplexHzList[i]; var p1 = mComplexHzList[mComplexHzList.Count - 1 - i]; var p = WWPolynomial.Add(p0, p1).ToRealPolynomial(); mRealHzList.Add(p); } { var p = mComplexHzList[mComplexHzList.Count / 2]; mRealHzList.Add(new RealRationalPolynomial( new double[] { p.N(0).real }, new double[] { p.D(0).real, p.D(1).real })); } var gainR = 0.0; foreach (var p in mRealHzList) { gainR += p.Evaluate(1.0); } Console.WriteLine("gainR={0}", gainR); } TransferFunction = (WWComplex z) => { return(TransferFunctionValue(z)); }; // ポールの位置 = mHzListの多項式の分母のリストから判明する。 mPoleArray = new WWComplex[mComplexHzList.Count]; for (int i = 0; i < mComplexHzList.Count; ++i) { var p = mComplexHzList[i]; Console.WriteLine(" {0} {1}", i, p); mPoleArray[i] = WWComplex.Div(p.D(0), p.D(1)).Minus(); } { // 零の位置を計算する。 // 合体したH(z)の分子の実係数多項式の根が零の位置。 var poly = mH_z.NumerPolynomial(); var coeffs = new double[poly.Degree + 1]; for (int i = 0; i < coeffs.Length; ++i) { coeffs[i] = poly.C(i).real; } var rf = new JenkinsTraubRpoly(); bool b = rf.FindRoots(new RealPolynomial(coeffs)); if (b) { Console.WriteLine("■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■"); Console.WriteLine("polynomial degree = {0}, {1}/{2}", mH_z.Degree(), mH_z.NumerDegree(), mH_z.DenomDegree()); Console.WriteLine("Hz={0}", mH_z.ToString("z^-1")); mZeroArray = rf.RootArray(); foreach (var r in mZeroArray) { Console.WriteLine(" zero at {0}", WWComplex.Reciprocal(r)); } } } }
/// <summary> /// アナログフィルターの伝達関数psをBilinear変換器に投入する。 /// </summary> public void Add(FirstOrderComplexRationalPolynomial ps) { mH_s.Add(ps); mComplexHzList.Add(StoZ(ps)); }