Persistence

Persistence refers to the notion that the state of a client is reachable after a model execution. Once a model has ended, the actors within the model have been consumed but some clients are still available. They are the clients from actors that have been given the client pointer and not the client itself. We have already witnessed the persistence of the clients in the previous section with the Logging clients from which data is processed after the models have been terminated.

In the following, we are going to make use of this property of the clients to update a model at different stage of its execution.

The model is a feedback loop system which is bootstrapped with a low gain (0.2) for 1s, then the gain is increased to 0.5 for another 3s and finally the sampling rate of the feedback loop is reduced to 1/100th for the rest of the simulation.

Lets first define the simulation sampling frequency (1kHz) and the durations of the 3 stages of the simulation:

    let sim_sampling_frequency = 1000; //Hz
    let sampling_frequency_hz = sim_sampling_frequency as f64;
    let bootstrap_duration = 1; // s
    let fast_high_gain_duration = 3; // s
    let slow_high_gain_duration = 4; // s

The parameters above are used to defined the number of samples for each stage:

    let n_bootstrap = bootstrap_duration * sim_sampling_frequency;
    let n_fast_high_gain = fast_high_gain_duration * sim_sampling_frequency;
    let n_slow_high_gain = slow_high_gain_duration * sim_sampling_frequency;
    let n_step = n_bootstrap + n_fast_high_gain + n_slow_high_gain;

The input signal is the sum of 3 signals: 2 sinusoides and some white noise:

    let signal = Signals::new(1, n_step)
        .channel(
            0,
            Signal::Sinusoid {
                amplitude: 0.5f64,
                sampling_frequency_hz,
                frequency_hz: 1_f64,
                phase_s: 0f64,
            } + Signal::Sinusoid {
                amplitude: 0.1f64,
                sampling_frequency_hz,
                frequency_hz: 10_f64,
                phase_s: 0.1f64,
            } + Signal::WhiteNoise(Normal::new(-1f64, 0.005)?),
        )
        .into_arcx();

Next we set the other 2 persistent clients for:

  • the feedback integral control with the gain set to default (0)
    let integrator = Integrator::new(1).into_arcx();
  • and data logging
    let logging = Logging::<f64>::new(2).into_arcx();

For stage I and II, the models are the same, only the gain of the integral controller is updated to 0.2 for stage I and to 0.5 for stage II. So we define a closure that represents the model template for stage I and II:

    let model = |n| -> anyhow::Result<Model<Unknown>> {
        let mut timer: Initiator<Timer, 1> = Timer::new(n).into();
        let mut source: Actor<_> = Actor::new(signal.clone());
        let mut sum: Actor<_> = (Sum::default(), "+").into();
        let mut feedback: Actor<_> = Actor::new(integrator.clone());
        let mut logger: Terminator<_> = Actor::new(logging.clone());

        timer.add_output().build::<Tick>().into_input(&mut source)?;
        source
            .add_output()
            .multiplex(2)
            .build::<U>()
            .into_input(&mut sum)
            .into_input(&mut logger)?;
        sum.add_output()
            .multiplex(2)
            .build::<E>()
            .into_input(&mut feedback)
            .into_input(&mut logger)?;
        feedback
            .add_output()
            .bootstrap()
            .build::<Y>()
            .into_input(&mut sum)?;

        Ok(model!(timer, source, sum, feedback, logger))
    };

The only argument to the closure is the duration n of each stage. In addition to the actors of the signal, integrator and logging clients, actors for the Timer and the Sum clients were added. The Sum client is the same that the one introduces in the FeedBack System section.

The model for stage I and II looks like this:

stage I & II model

Stage I

For stage I, we create the model with the appropriate duration, set the integrator gain and run the model:

    let stage_i = model(n_bootstrap)?
        .name("persistence-stage-I")
        .flowchart()
        .check()?;
    (*integrator.lock().await).set_gain(0.2);
    let stage_i = stage_i.run();

Stage II

For stage II, we also start by creating the model with the appropriate duration, then we wait for stage I to finish before setting the integrator gain for stage II and running the model with the updated gain:

    let stage_ii = model(n_fast_high_gain)?
        .name("persistence-stage-II")
        .flowchart()
        .check()?;
    stage_i.await?;
    (*integrator.lock().await).set_gain(0.5);
    let stage_ii = stage_ii.run();

Stage III

For stage III, the feedback loop sampling rate is reduced by a factor 100:

const C: usize = 100;

The input signal is average over 100 samples and the output signal is upsampled by a factor 100 as well. Considering the above, the stage III actors are:

    let mut source: Initiator<_> = Actor::new(signal.clone());
    let mut avrg: Actor<_, 1, C> = Average::new(1).into();
    let mut sum: Actor<_, C, C> = (Sum::default(), "+").into();
    let mut feedback: Actor<_, C, C> = Actor::new(integrator.clone());
    let mut upsampler: Actor<_, C, 1> = Sampler::new(vec![0f64]).into();
    let mut logger: Terminator<_> = Actor::new(logging.clone());

We removed the Timer client and added actors for the Average and Sampler clients and we are still using the same Signals, Integrator and Logging clients but in the state they will be at the end of stage II.

The stage III network is build next:

    source
        .add_output()
        .multiplex(2)
        .build::<U>()
        .into_input(&mut avrg)
        .into_input(&mut logger)?;
    avrg.add_output().build::<U>().into_input(&mut sum)?;
    sum.add_output()
        .multiplex(2)
        .build::<E>()
        .into_input(&mut feedback)
        .into_input(&mut upsampler)?;
    upsampler
        .add_output()
        .bootstrap()
        .build::<E>()
        .into_input(&mut logger)?;
    feedback
        .add_output()
        .bootstrap()
        .build::<Y>()
        .into_input(&mut sum)?;

stage III model

The Sampler output had to be bootstrapped as the input is delayed by 100 samples. That is the reason why the Sampler has been creating with a default input value:

    let mut upsampler: Actor<_, C, 1> = Sampler::new(vec![0f64]).into();

Finally, we create the new model, waiting for stage II to finish before running it:

    let stage_iii = model!(source, avrg, sum, feedback, upsampler, logger)
        .name("persistence-stage-III")
        .flowchart()
        .check()?;
    stage_ii.await?;
    stage_iii.run().await?;

The logged data is plotted with:

    let _: complot::Plot = (
        (*logging.lock().await)
            .chunks()
            .enumerate()
            .map(|(i, data)| (i as f64 / sampling_frequency_hz, data.to_vec())),
        complot::complot!("persistence.png", xlabel = "Time [s]"),
    )
        .into();

The blue curve is the input signal (U) and the orange curve is the residual signal (E) at the output of the sum.

3 stages model

The data corresponding to the transition for one stage to the next is displayed with:

  • stage I to stage II transition:
   println!("Stage I to Stage II transition:");
   (*logging.lock().await)
       .chunks()
       .enumerate()
       .skip(n_bootstrap - 5)
       .take(10)
       .for_each(|(i, x)| println!("{:4}: {:+.3?}", i, x));

stage I to stage II transition

  • stage II to stage III transition:
   println!("Stage II to Stage III transition:");
   (*logging.lock().await)
       .chunks()
       .enumerate()
       .skip(n_bootstrap + n_fast_high_gain - 5)
       .take(10)
       .for_each(|(i, x)| println!("{:4}: {:+.3?}", i, x));

stage II to stage III transition

and the data accross the end of the 1st integration of stage III

   println!("Stage III (1st integration):");
   (*logging.lock().await)
       .chunks()
       .enumerate()
       .skip(C + n_bootstrap + n_fast_high_gain - 5)
       .take(10)
       .for_each(|(i, x)| println!("{:4}: {:+.3?}", i, x));

stage III 1st integration