简体   繁体   English

使用 Rx.NET 进行信号过滤

[英]Signal Filter with Rx.NET

I'd like to implement a signal filter in Rx.NET which starts with an initial set of coefficients.我想在 Rx.NET 中实现一个信号滤波器,它从一组初始系数开始。 As time goes by the filter coefficients have to be recalculated from a snapshot of observed data values.随着时间的推移,必须根据观察到的数据值的快照重新计算滤波器系数。

Here is a small prototype which shows how it should work.这是一个小原型,展示了它应该如何工作。 For simplicity I choose the filter length and the number of the historical data values used to recalculate the filter coefficients to be the same (3 in the example).为简单起见,我选择滤波器长度和用于重新计算滤波器系数的历史数据值的数量相同(示例中为 3)。

The example uses a side effect in bufferedAt10 to recalculate the coefficients.该示例使用bufferedAt10的副作用来重新计算系数。 This is not what I would like to have.这不是我想要的。

In the real application the data is coming in irregular time steps and the coefficient should be updated once a day or once a week at a specific time.在实际应用中,数据以不规则的时间步长出现,系数应每天或每周在特定时间更新一次。 I can easily make the buffer longer, but how can I let the system run and change the filter coefficients from a observer in a clean functional way?我可以轻松地延长缓冲区,但是如何让系统运行并以干净的功能方式从观察者更改滤波器系数?

       // create a hot obvervable to produce data
        const int bufLen = 3;
        var rng = new Random();
        var period = TimeSpan.FromSeconds(0.5);
        var observable = Observable.Interval(period)
        .Select(i => new {Time = DateTime.Now, Price = rng.NextDouble()})
        .Do(e => Console.WriteLine("original : {0}", e))
        .Publish();
        observable.Connect();

        Console.WriteLine("Press any key to subscribe");
        Console.ReadKey();

        // buffer of length bufLen used for filter calculation (every tick) and filter 
        // coefficient update (at a lower frequency)
        var buffered = observable.Buffer(bufLen, 1);

        // apply the signal filter with coefficients in `coeff`
        var coeff = new List<Double>() {1.0, 1.0, 1.0};  // these will be updated on the way from new data 
        var filtered = buffered.Select(e =>
        {
            var f = 0.0;
            for (var i = 0; i < bufLen; i++)
            {
                f += e[i].Price*coeff[i]; // apply the filter with coefficients `coeff`
            }
            return new {Time = DateTime.Now, FilteredPrice = f};
        });

        var s1 = filtered.Subscribe(e => Console.WriteLine("filtered : {0} (coeff {1},{2},{3})", e, coeff[0], coeff[1], coeff[2]));

        // recalculate the filter coefficients say every 10 seconds 
        var bufferedAt10 = buffered.DistinctUntilChanged(e => (e[bufLen - 1].Time.TimeOfDay.Seconds / 10) * 10);

        var s2 = bufferedAt10.Subscribe(e =>
        {
            Console.WriteLine("recalc of coeff : {0}", e[bufLen - 1].Time);
            for (var i = 0; i < bufLen; i++)
            {
                // a prototypical function that takes the buffer and uses it to "recalibrate" the filter coefficients
                coeff[i] = coeff[i] + e[bufLen - 1 - i].Price;
            }
            Console.WriteLine("updated coeffs to {0},{1},{2}", coeff[0], coeff[1], coeff[2]);
        });

Thanks for any good advice.感谢您提供任何好的建议。

The following is untested but I think it should cover what you need.以下内容未经测试,但我认为它应该涵盖您的需求。 The idea behind it is that you diverge the streams do the coefficient updates on one, and then bring them back together with WithLatestFrom .其背后的想法是,您将流分流进行系数更新,然后将它们与WithLatestFrom结合在一起。 I used Sample and Scan to perform the period "adjustment".我使用SampleScan来执行周期“调整”。 And your custom timestamping can be done by using the TimeStamp operator instead.您的自定义时间戳可以通过使用TimeStamp运算符来完成。 You may also consider moving the Publish down past Buffer otherwise you will have two streams generating buffers, but it is up to you.您也可以考虑将Publish向下移动经过Buffer否则您将有两个流生成缓冲区,但这取决于您。

const int bufLen = 3;
var rng = new Random();
var period = TimeSpan.FromSeconds(0.5);
var observable = Observable.Interval(period)
  .Select( => rng.NextDouble())
  .Publish();
observable.Connect();

Console.WriteLine("Press any key to subscribe");
Console.ReadKey();

var buffered = observable.Buffer(bufLen, 1);

var seed = new [] {1.0, 1.0, 1.0};

var coefficients = buffered
                     //Samples for a new value every 10 seconds
                     .Sample(TimeSpan.FromSeconds(10))
                     //Updates the seed value and emits it after every update
                     .Scan(seed, 
                       //Use good old fashion Linq
                       (coeff, delta) => coeff.Zip(delta.Reverse(), 
                                         (c, d) => c + d.Price)
                                         .ToArray()
                       );

//Emits a new value everytime buffer emits, and combines it with the latest
//values from the coefficients Observable
//Kick off coefficients with the seed otherwise you need to wait 10 seconds 
//for the first value.
buffer.WithLatestFrom(coefficients.StartWith(seed), (e, coeff) => {
  return e.Zip(coeff, (x, c) => x.Price * c).Sum();
})
.TimeStamp()
.Subscribe(e => Console.WriteLine("filtered : {0}", e);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM