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

add sbml infix parsing and printing #1785

Merged
merged 16 commits into from Jun 10, 2021
Merged

Conversation

lkeegan
Copy link
Member

@lkeegan lkeegan commented May 5, 2021

  • add parser function parse_sbml()
  • add print function sbml()
  • add sbml_tokenizer.*, sbml_parser.* to symengine/parser/sbml (see design question: multiple parsers #1780)
  • use api.prefix {sbml} in sbml_parser.yy to avoid nameclashes with main parser
  • sbml_parser.yy is based on Simplify the parser #1596
  • sbml infix format is described here
  • apologies for the large PR!

- add parser function `parse_sbml()`
- add print function `sbml()`
- add sbml_tokenizer, sbml_parser, etc to symengine/parser/sbml
- use `api.prefix {sbml}` in sbml_parser.yy to avoid nameclashes with main parser
@rikardn
Copy link
Contributor

rikardn commented May 5, 2021

I am excited about this PR! I don't think it is related, but we need to do something about the fail of the test_tutorials. It is always nice to have everything in green before reviewing PRs.

+ "' has no arguments");
}

RCP<const Basic> SbmlParser::functionify(const std::string &name,
Copy link
Member

Choose a reason for hiding this comment

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

We can reduce the code duplication here using the same trick as init_sbml_printer_names. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried this here: a159dcd

Is that what you meant? If so, it does reduce duplication, but it might also require more maintenance - e.g. if a new function is added to the main parser that is not supported by sbml, it would also have to be explicitly removed from the sbml parser. Either way is fine by me though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Having to remember to remove something in one end when something was added in another sounds like an extra maintenance burden. Don't we want to avoid that?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, looks good to me.

if a new function is added to the main parser that is not supported by sbml, it would also have to be explicitly removed from the sbml parser

Is it necessary to remove them though? Would it be bad if they were kept?

Copy link
Member Author

@lkeegan lkeegan May 6, 2021

Choose a reason for hiding this comment

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

Yes, looks good to me.

if a new function is added to the main parser that is not supported by sbml, it would also have to be explicitly removed from the sbml parser

Is it necessary to remove them though? Would it be bad if they were kept?

  • it could mean invalid sbml infix gets parsed as valid
    • but it would give an error when printing: so that avoids the invalid sbml from being exported somewhere else
    • it would also print without errors, since sbml inherits the default function names, but the resulting sbml infix wouldn't be valid
  • if a function is added to the main parser with the same name as an existing sbml one it would take precedence (since insert is a no-op if the key already exists), which could potentially change the meaning of this function in the sbml parser
    • but this would then cause a sbml_parser test to fail

tldr; no, it's probably fine to keep them

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess the question is what should the sbml parser/printer do with math that the main parser+symengine understands, but that is not part of sbml?

  1. Raise an error
  • need to define all valid functions -> code duplication
  • or inherit default functions & remove invalid ones -> maintenance burden when defaults change
  1. Fallback to the default printer/parser behaviour
  • no code duplication or maintenance issues

Currently we do 1. when parsing sbml, and 2. when printing sbml

  • As a user I would prefer option 1. for both printing & parsing: raising an error is much more helpful than silently parsing/producing invalid infix
  • But the current setup is also fine, since the user can just re-parse the printed output to check it is valid
  • As a maintainer I would probably prefer to implement this by defining all valid functions (i.e. duplication rather than maintenance) but either way is fine by me

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I also think the parser should throw on illegal sbml. Also the printer should throw for things that cannot be supported or isn't currenty supported in sbml. Some cases I guess could be supported with workarounds if needed. For example we have a printer for Fortran code in Pharmpy that converts sign(x) to ABS(X) / X (if x is known to be positive) or similar.

Copy link
Member Author

Choose a reason for hiding this comment

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

@isuruf what are your thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

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

I'm okay with either.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok, I'll keep the current behaviour for now then (sbml parser throws on invalid sbml, sbml printer falls back to default printer), and leave improving the printer to throw on invalid sbml for a future PR.

I've reverted to defining all sbml parser functions to avoid having to update the sbml parser when new functions are added to the main parser (4054db5)

namespace SymEngine
{

class SbmlParser
Copy link
Member

Choose a reason for hiding this comment

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

Can you make this inherit from the string parse so that parse_numeric and other functions need not be duplicated?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes - after struggling with strange compiler errors it turns out this also required a change to the main parser: b9d40f8

- bison uses `YYSTYPE` internally, and defines a macro to refer to the actual type, e.g. `#define YYSTYPE ParserSType`
- when including multiple parsers this macro expansion resulted in missing/re- definition errors
@lkeegan lkeegan mentioned this pull request May 6, 2021
6 tasks
Comment on lines 94 to 105
functions.insert({"arcsin", asin});
functions.insert({"arccos", acos});
functions.insert({"arctan", atan});
functions.insert({"arcsec", asec});
functions.insert({"arccsc", acsc});
functions.insert({"arccot", acot});
functions.insert({"arcsinh", asinh});
functions.insert({"arccosh", acosh});
functions.insert({"arctanh", atanh});
functions.insert({"arcsech", asech});
functions.insert({"arccoth", acoth});
functions.insert({"arccsch", acsch});
Copy link
Member

Choose a reason for hiding this comment

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

I'm fine with adding these into the main parser.

Copy link
Member Author

Choose a reason for hiding this comment

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

done here: 8b80e66

@lkeegan
Copy link
Member Author

lkeegan commented Jun 7, 2021

thanks @rikardn and @isuruf for reviewing! I think this is ready to merge if ok with you?

@rikardn
Copy link
Contributor

rikardn commented Jun 7, 2021

I think the code is looking good. Would it be possible to keep the code coverage above 90% ?

@lkeegan
Copy link
Member Author

lkeegan commented Jun 8, 2021

@rikardn I added a couple more tests (and found #1804 in the process), but I don't think the coverage will get much higher: nearly all the written code is fully covered - the remaining uncovered code is in the source files auto-generated by re2c/bison & appears to be mostly error handling or implementing things that we don't use: https://app.codecov.io/gh/symengine/symengine/compare/master...sbml_parser/diff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants