-
Notifications
You must be signed in to change notification settings - Fork 80
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
Integrate sin, cos and constants to Storm Jani parser #556
Integrate sin, cos and constants to Storm Jani parser #556
Conversation
be3eeb3
to
77ad64e
Compare
@@ -1432,6 +1442,16 @@ storm::expressions::Expression JaniParser<ValueType>::parseExpression(Json const | |||
STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Unknown operator " << opstring << " in " << scope.description << "."); | |||
} | |||
} | |||
if (expressionStructure.count("constant") == 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, the expression now represents an approximation of the actual expression in the jani file. This is something to discuss a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand it correctly, you suggest to have something like this for PI (and the same for e
):
if (constantStr == "π") {
return storm::expressions::pi();
}
Did I get it right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello,
I started looking into implementing this and I am not sure on what would be the preferred way to go.
My current understanding is that we need to have an additional class inherited from BaseExpression
, that we can call ConstantValueExpression
(or anything else), and this would provide an enum for the ConstantValue to store (i.e. PI and E), together with the same methods as for the RationalLiteralExpression
class.
Taking this path means that some more boilerplate code needs to be written (for now I only found all the classes inherited from ExpressionVisitor
, but maybe there is more), so I want to ask whether this is what you had in mind with your comment above, before I start implementing everything that is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tquatmann your input would be greatly appreciated here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally would indeed tend to use a ConstantValueExpression or maybe a RealLiteralExpression?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you describe is the most correct and most complete way to implement this. However, unless someone sees an important use case for π and e outside of jani models, I suggest to reduce the mentioned boilerplate code a bit:
-
Put your new expression class here, where we collect all expressions that are somewhat special for JANI. Given the current naming scheme, the new class should have suffix
...LiteralExpression
, maybeTranscendentalNumberLiteralExpression
? -
Extend the JaniExpressionVisitor and every class that inherits from this (fortunately, these are only a hand full and it's almost always clear what to do).
-
Extend the
substituteConstantsInPlace
function of the JANI model, so that it can (optionally, controlled by an argument) approximate transcendentalNumberLiterals. Apply this substitution, before building the model.
The advantage of this approach is that you can still export the parsed jani without approximating the constants.
For example, when using storm-conv --jani input.jani --constants N=4 --tojani output.jani
, the constants should still be present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, having the new ...LiteralExpression
class in jani/expressions
seems more appropriate for the current PR, thanks for your suggestion!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @tquatmann ,
I just pushed a first attempt at integrating the TranscendentalNumberLiteralExpression
in the JaniParser.
I am not sure this is 100% what you had in mind, since it resulted in an unexpected amount of changes around the code. Please let me know if there is anything that you think should be done differently.
Also, the unit tests and all the remaining comments will come at a later commit. This is just to make sure the changes related to this comment match your requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just pushed some more changes I needed to do in order to have storm-conv working as you described.
Somehow this ended up touching an unexpected amount of files, I hope this is still fine with you, but of course I am open to any suggestion you might have.
In the next days I will address the last few comments you had (namely a missing type check and accepting integers as argument of trigonometric operators).
Hi Marco, great!
Best, |
Hi Sebastian, thanks for the very quick feedback!
Best, |
case OperatorType::Cos: | ||
case OperatorType::Sin: | ||
// The operands expect a real value. Raise an exception | ||
STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Cannot apply cos or sin to an integer value."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding of the JANI-spec is that e.g. sin(10)
would be valid in JANI as int is also a valid numeric type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is true. At the same time, the operator's output will always be a rational value, regardless of the input type. Anyway, I will add support for having integers as input.
Signed-off-by: Marco Lampacrescia <[email protected]>
Signed-off-by: Marco Lampacrescia <[email protected]>
77ad64e
to
6fb451b
Compare
Signed-off-by: Marco Lampacrescia <[email protected]>
…the model Signed-off-by: Marco Lampacrescia <[email protected]>
…nput type Signed-off-by: Marco Lampacrescia <[email protected]>
Hello @sjunges and @tquatmann , I just pushed the last changes related to this PR. Now it should be good to go, if you do not spot anything else to change. :) Looking forward to your inputs. Best, |
src/storm/storage/jani/Model.cpp
Outdated
return *this; | ||
} | ||
|
||
Model Model::substituteConstants() const { | ||
Model result(*this); | ||
result.replaceUnassignedVariablesWithConstants(); | ||
result.substituteConstantsInPlace(); | ||
result.substituteConstantsInPlace(false); | ||
return result; | ||
} | ||
|
||
Model Model::substituteConstantsFunctions() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added to this method the substitution of Transcendental numbers with Rational numbers, too.
Do you think it makes sense to reflect that in the name of the method, too? Something like substituteConstantsFunctionsTranscendentals
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, it is a bit inconsistent that substituteConstants
does not substitute transcendentals, while substituteConstantsFunctions
does. Changing the name as you suggest sounds good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, will do it!
@@ -156,6 +158,7 @@ bool JaniNextStateGenerator<ValueType, StateType>::canHandle(storm::jani::Model | |||
features.remove(storm::jani::ModelFeature::DerivedOperators); | |||
features.remove(storm::jani::ModelFeature::Functions); // can be substituted | |||
features.remove(storm::jani::ModelFeature::StateExitRewards); | |||
features.remove(storm::jani::ModelFeature::TrigonometricFunctions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this true? If so, I think this should be covered in a test.
If you are not sure, I would not allow this right now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello Sebastian,
I am using it in the smc extension we are developing in the CONVINCE, so I am quite convinced this is working.
However, having one more test never hurts! :)
Some references about the smc use case:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to make sure that we dont break this, a test definitively does not hurt :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I adapted the test_trigonometry jani file to not expand forever and added a test for it in the ExplicitJaniModelBuilderTest
gtest, (that is the only place I found with similar tests).
The new unit test can be found in this commit: 6b54f7c
On doing it, I found that transcendental numbers were not handled correctly in the constant variables bounds, but now they are handled, too.
@@ -187,7 +196,7 @@ class IntegerType : public BaseType { | |||
virtual bool isIntegerType() const override; | |||
|
|||
private: | |||
static const uint64_t mask = (1ull << 62); | |||
static const uint64_t mask = (1ull << 58); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason these have changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was required for the type comparison here implemented here: to ensure that a transcendental number type is considered a non-integer number by the various model visitors, its mask must evaluate to a value higher than the one inn IntegerType.
Since in this implementation the mask is generated by setting a single bit in a uint64 variable, RationalType was getting the highest possible value, and IntegerType the one right before that. For this reason, I had to shift all of them to make space for the newly implemented TranscendentalNumberType.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the shift decreased by 4 and not just by 1? My understanding is that masks (1ull << 61)
, (1ull << 62)
and (1ull << 63)
are now unused, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When introducing the TranscendentalNumbers, I had to do the shift because I needed the related type mask value to be greater than the rational numbers one, that was using the highest possible value.
While implementing the shift, I thought that it would have been much easier to add the new mask if the last value wasn't already used up, and following that reasoning, I made sure that there were some unused values before and after the existing Type masks.
Functionality wise, this makes no difference, but it makes it easier to add new types in the future, if that will be ever needed.
Anyway, if you prefer to have it as it was before, I can make sure there are no unused values starting from the last one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for clarifying. I just wanted to make sure that I didn't miss some detail concerning the unused masks.
void TemplateEdgeDestination::substitute(std::map<storm::expressions::Variable, storm::expressions::Expression> const& substitution) { | ||
assignments.substitute(substitution); | ||
void TemplateEdgeDestination::substitute(std::map<storm::expressions::Variable, storm::expressions::Expression> const& substitution, | ||
bool const& substituteTranscendentalNumbers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool by const& triggers a warning on my system
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove the reference. On my system quite some warning are triggered, so it is hard to notice new ones.
Are there more warnings that were added by this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a look in the repository and bool const&
seems to be a common pattern for function definitions in STORM.
Of course I can switch the ones I added to be bool const
, by dropping the reference, but maybe it would make sense to make that consistent throughout the whole repository. The same could be done for all other basic types (int, float, double, char).
In case, I believe that should be tackled in a separated PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed all declarations of bool const& substituteTranscendentalNumbers
to not pass the value by reference anymore here: 6f016ca
On my machine, this seems not to throw warnings anymore.
|
||
for (auto& assignment : assignments) { | ||
assignment.substitute(substitution); | ||
assignment.substitute(substitution, substituteTranscendentalNumbers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this maybe something that can be avoided by having a sensible default for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure, since this is set in the JaniModel's methods to substitute constants (around here) and must be propagated down to the various entries of the model. The value depends whether we want to preserve the transcendental numbers (e.g. storm-conv) or not (when we run the evaluation).
A way to not have the this argument would be to set a static variable for the jani visitor, to signal whether we want to substitute transcendental numbers or not, but I wouldn't recommend it, since it makes the code execution less clear.
Signed-off-by: Marco Lampacrescia <[email protected]>
Signed-off-by: Marco Lampacrescia <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing all this! 👍
There are some rather minor comments left. Apart from that, this is looking good!
I believe we could eventually replace all these substitute
methods by a JaniTraverser
but that is something for a different PR.
@@ -187,7 +196,7 @@ class IntegerType : public BaseType { | |||
virtual bool isIntegerType() const override; | |||
|
|||
private: | |||
static const uint64_t mask = (1ull << 62); | |||
static const uint64_t mask = (1ull << 58); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the shift decreased by 4 and not just by 1? My understanding is that masks (1ull << 61)
, (1ull << 62)
and (1ull << 63)
are now unused, correct?
src/storm/storage/jani/Model.cpp
Outdated
return *this; | ||
} | ||
|
||
Model Model::substituteConstants() const { | ||
Model result(*this); | ||
result.replaceUnassignedVariablesWithConstants(); | ||
result.substituteConstantsInPlace(); | ||
result.substituteConstantsInPlace(false); | ||
return result; | ||
} | ||
|
||
Model Model::substituteConstantsFunctions() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, it is a bit inconsistent that substituteConstants
does not substitute transcendentals, while substituteConstantsFunctions
does. Changing the name as you suggest sounds good to me.
@@ -60,7 +60,7 @@ std::shared_ptr<BaseExpression const> ConstructorArrayExpression::size() const { | |||
std::shared_ptr<BaseExpression const> ConstructorArrayExpression::at(uint64_t i) const { | |||
std::map<storm::expressions::Variable, storm::expressions::Expression> substitution; | |||
substitution.emplace(indexVar, this->getManager().integer(i)); | |||
return storm::jani::substituteJaniExpression(elementExpression->toExpression(), substitution).getBaseExpressionPointer(); | |||
return storm::jani::substituteJaniExpression(elementExpression->toExpression(), substitution, true).getBaseExpressionPointer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return storm::jani::substituteJaniExpression(elementExpression->toExpression(), substitution, true).getBaseExpressionPointer(); | |
return storm::jani::substituteJaniExpression(elementExpression->toExpression(), substitution, false).getBaseExpressionPointer(); |
If I see this correctly, one wouldn't want to substitute transcendentalNumbers at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right, I am switching it to false in the upcoming commit!
@@ -66,10 +66,10 @@ storm::expressions::Expression const& BoundedType::getUpperBound() const { | |||
void BoundedType::substitute(std::map<storm::expressions::Variable, storm::expressions::Expression> const& substitution) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this not pass a bool substituteTranscendentalNumbers
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, I was just afraid of growing the amount of changes too large, but looking at that again, there is no reason to not have the substituteTranscendentalNumbers flag here as well. I will add that to the upcoming commit!
…bstitution method in JaniModel Signed-off-by: Marco Lampacrescia <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, thanks for your review.
I addressed the comments from @tquatmann and pushed them in the last commit, and I added one comment about the bit shift for the Type maks.
Let me know what you think about it.
@@ -187,7 +196,7 @@ class IntegerType : public BaseType { | |||
virtual bool isIntegerType() const override; | |||
|
|||
private: | |||
static const uint64_t mask = (1ull << 62); | |||
static const uint64_t mask = (1ull << 58); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When introducing the TranscendentalNumbers, I had to do the shift because I needed the related type mask value to be greater than the rational numbers one, that was using the highest possible value.
While implementing the shift, I thought that it would have been much easier to add the new mask if the last value wasn't already used up, and following that reasoning, I made sure that there were some unused values before and after the existing Type masks.
Functionality wise, this makes no difference, but it makes it easier to add new types in the future, if that will be ever needed.
Anyway, if you prefer to have it as it was before, I can make sure there are no unused values starting from the last one.
@@ -60,7 +60,7 @@ std::shared_ptr<BaseExpression const> ConstructorArrayExpression::size() const { | |||
std::shared_ptr<BaseExpression const> ConstructorArrayExpression::at(uint64_t i) const { | |||
std::map<storm::expressions::Variable, storm::expressions::Expression> substitution; | |||
substitution.emplace(indexVar, this->getManager().integer(i)); | |||
return storm::jani::substituteJaniExpression(elementExpression->toExpression(), substitution).getBaseExpressionPointer(); | |||
return storm::jani::substituteJaniExpression(elementExpression->toExpression(), substitution, true).getBaseExpressionPointer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right, I am switching it to false in the upcoming commit!
src/storm/storage/jani/Model.cpp
Outdated
return *this; | ||
} | ||
|
||
Model Model::substituteConstants() const { | ||
Model result(*this); | ||
result.replaceUnassignedVariablesWithConstants(); | ||
result.substituteConstantsInPlace(); | ||
result.substituteConstantsInPlace(false); | ||
return result; | ||
} | ||
|
||
Model Model::substituteConstantsFunctions() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, will do it!
@@ -66,10 +66,10 @@ storm::expressions::Expression const& BoundedType::getUpperBound() const { | |||
void BoundedType::substitute(std::map<storm::expressions::Variable, storm::expressions::Expression> const& substitution) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, I was just afraid of growing the amount of changes too large, but looking at that again, there is no reason to not have the substituteTranscendentalNumbers flag here as well. I will add that to the upcoming commit!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking into my comments. To me, this PR looks ready to be merged!
Many thanks for all the work. |
I am the one thanking you for the discussions and for looking into this :) |
This PR introduces support to the
sin
andcos
operators, and to the PI and Euler number constants.In particular, for the sin and cos operators I introduced a new expression type called
trigonometric
, that can be used later on in case other trigonometric operators will be integrated in storm.Please let me know if I missed any additional place where the aforementioned operators need to be defined.