Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time dependent modifiers #522

Merged
merged 5 commits into from
Feb 6, 2020
Merged

Conversation

eidekrist
Copy link
Member

This PR is a different take on PR #463, aiming to solve #203. The difference this time is that the API now supports modifier functions which depend on time information, and that these only need to be set once.

Premise:

  • I want to set a modifier on an unconnected input variable. The modifier should add a bias to the variable value, which is gradually ramped up with each step.
  • I want to specify the slope of the ramp with unit per second - such that a slope value of 2.0 increases the variable value with 2.0 over the duration of one second.

In order to achieve this, two changes are necessary:

  • To apply the slope value correctly, the modifier function needs access to the current step size, since I can't be sure which step size the algorithm will use.
  • In order to gradually increase the modified value with each step, the modifier function must be run on each step, regardless if a value has been set from the outside.

Discussion needed: The last point breaks the "simulation correctness" principle discussed earlier when connecting a slow simulator to a faster one (but only if setting a modifier function). Then again, if you are modifying a variable, simulation correctness is probably not your top priority.

The modifier functions now take the form of

std::function<value_type(value_type, cse::duration)>

where cse::duration represents the step size for "this step". I tried finding a reason/use case for also including the current simulation time, but was unsuccessful, so I left it out.

@eidekrist eidekrist added enhancement New feature or request discussion needed Let's have a discussion about this labels Feb 4, 2020
@eidekrist eidekrist self-assigned this Feb 4, 2020
@kyllingstad
Copy link
Member

Discussion needed: The last point breaks the "simulation correctness" principle discussed earlier when connecting a slow simulator to a faster one (but only if setting a modifier function). Then again, if you are modifying a variable, simulation correctness is probably not your top priority.

Not sure I understood this. Can you elaborate and/or give an example of what you mean?

@kyllingstad
Copy link
Member

I tried finding a reason/use case for also including the current simulation time, but was unsuccessful, so I left it out.

If the functions only receive step sizes, any function that depends on absolute time would have to maintain an accumulated sum of time steps. This makes it a bit impractical to use lambdas for such cases.

But let's keep it as-is for now and see how it works out. It's just a matter of adding an additional argument anyway.

@eidekrist
Copy link
Member Author

Not sure I understood this. Can you elaborate and/or give an example of what you mean?

Example:

  • Sim A has step size decimation factor 2.
  • Sim B has step size decimation factor 1.
  • Sim A's output is connected to Sim B's input.
  • In the normal case, values will be transferred from Sim A to Sim B every other base step, and we make sure Sim B -> setReal is only called on the steps where values are transferred through the connection. This is the "correctness" thing - Sim B might extrapolate its inputs since they were not set.
  • With the proposed changes, if setting a modifier function on Sim B's input, Sim B -> setReal will be called every single time Sim B is stepped.

Copy link
Member

@kyllingstad kyllingstad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice and clean solution!

@kyllingstad
Copy link
Member

  • In the normal case, values will be transferred from Sim A to Sim B every other base step, and we make sure Sim B -> setReal is only called on the steps where values are transferred through the connection. This is the "correctness" thing - Sim B might extrapolate its inputs since they were not set.
  • With the proposed changes, if setting a modifier function on Sim B's input, Sim B -> setReal will be called every single time Sim B is stepped.

Ok, I see now. Then I agree with you. This is something users will simply have to understand and accept if they use modifiers.

@kyllingstad
Copy link
Member

kyllingstad commented Feb 6, 2020

Hang on, don't merge quite yet. I had one review comment that seems to have been lost for some reason. Let me write it again so you can consider it before merging.

OK, done. :)

double slope = 1.0;
double* accumulator = &acc_;
f_ = std::function<double(double, cse::duration)>(
[=](double original, cse::duration deltaT) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the accumulator pointer, you can just use acc_ directly inside the lambda, because this is implicitly captured when you use [=].

Alternatively, if you want to avoid having to refer to an outside variable, you can add it to the lambda directly:

[=, acc = 0.0](double original, cse::duration deltaT) mutable { /* use and modify acc here */ }

If you keep your solution or use the former of mine, I suggest that you make the class noncopyable and nonmovable. Otherwise, the lambda will have a pointer to the class instance, and if the instance inadvertently gets moved to a different memory location, you're in trouble. (For a simple test this is no big risk, but it's a good habit to always do this when you leak this pointers to lambdas.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff, thanks! I learnt a lot from that. According to the fountain of wisdom making the class noncopyable also makes it nonmovable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of good advice in that SO answer that I wasn't aware of, thanks! Time to go undelete all of my deleted move constructors, brb. 😄

Copy link
Member

@ljamt ljamt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean solution and well documented with the test 👍

@eidekrist eidekrist merged commit ab37c48 into master Feb 6, 2020
@eidekrist eidekrist deleted the feature/203-stateful-modifiers branch February 6, 2020 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion needed Let's have a discussion about this enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants