-
Notifications
You must be signed in to change notification settings - Fork 2
Add thirty models #13
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
base: master
Are you sure you want to change the base?
Conversation
…tch to draw_graph
…arkov-builder into fix_transition_matrix
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #13 +/- ##
==========================================
+ Coverage 89.71% 93.30% +3.59%
==========================================
Files 5 22 +17
Lines 554 837 +283
==========================================
+ Hits 497 781 +284
+ Misses 57 56 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Add sundials install to workflow
MichaelClerx
left a comment
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 Joey,
Won't have time for an in-depth review (if you want that) till June or after.
Few small comments. And comparison 2 looks plain wrong?
Would plot an error for these 3 cases too (signal1 - signal2)
…arkov-builder into fix_transition_matrix
|
We don't have all 30 yet models yet, but there are some important changes to the transition-rate matrix calculations that it would be good to merge in |
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.
Initial review on the start of the main class.
Some patterns are emerging that you can fix elsewhere before I continue
- Try to keep the API minimal, providing access only where needed and using private variables to help the user know what to ignore. The point of a good class is that it hides details from the user
- Don't use logging everywhere in case you want to debug, some day. Avoid the need for debugging by having good tests. Add debug statements only when actually debugging and remove if needed. In Python this code is always run (unlike e.g. C macros), so it will slow things down for no good reason
- Stop calling things x_dict
- Describe all arguments in the class/function docstring.
- If there's any relations between argument (e.g. x is only checked if y has a certain value) then (1) see if you can design it better so that this doesn't occur; (2) if you really can't then check and raise ValueErrors when the user does it wrong; (3) document exactly what the allowable input is
| # between the two nodes | ||
|
|
||
| self.graph = nx.DiGraph() | ||
| if states: |
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.
Since these graphs don't seem to have any kind of typing, you'll need a big comment above to explain what goes in the graph, what is stored in its nodes, etc.
Also, I'd strongly recommond making as many variables as you can private. It says to the user of this class "You don't need to know how this bit works, leave this to me". If they are public, then define them at class level first and provide a docstring for each
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 deciding what to hide from the user, err on the side of hiding absolute everything. It's easy to provide access later. Initially, only write for the use case you know. If more are found later it's an easy job to provide get/set
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.
Will set things to private in a separate PR. Want to make a nice version available without breaking anything external too much (my other projects)
markov_builder/MarkovChain.py
Outdated
| def __init__(self, states: list = [], state_attributes_class: | ||
| MarkovStateAttributes = None, seed: int = None, | ||
| name: str = None): | ||
| MarkovStateAttributes = None, seed: int = None, name: str = |
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.
The docstring is not very clear. How is markov chain also a compartmental model?
The "using networkx" bit is exactly what a good class should hide from the user. If I'm using this class I don't want to know how you implemented it, I just need to know how to use it.
Instead of saying it has various functions and tests, tell me the main ones. Give an example of how I use it
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.
The () after the classname are unnecessary and best avoided, as it makes it look like a function
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.
First comment is fair, and I've taken it out of the docstring. But I think it might be worth mentioning that this could be used as a way of generating a suitable networkx graph so it can be manipulated / plotted / outputted with other tools. But admittedly, I didn't end up doing any of that.
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 get that. But it's generally a good guideline to only implement what you need. One good reason (beyond the simplicity and time saving mentioned below) is that it helps you test things: if you add a function you never use, you won't notice if it's broken. Much easier to just add it when you need to
https://dev.to/alisamir/yagni-you-arent-gonna-need-it-a-key-principle-in-software-development-167m
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's a good point. A lot of things in this package started out as a sort of prototype, so it'd be worth reevaluating what's actually needed
markov_builder/MarkovChain.py
Outdated
| except TypeError as exc: | ||
| logging.warning("Couldn't evaluate equilibrium distribution as float." | ||
| "Is every parameter defined?" | ||
| logging.warning("Error evaluating equilibrium distribution " |
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.
Raise an exception or if you reeeeeeally insist on warning but continuing use warnings.warn()
The logging module is for big server applications like apache or a DB or mail server or something.
If you want debug statements just use print and comment them out when not debugging (or simply remove them - again don't program for contingencies and eventualities, deal with them if they happen and add tests for the use cases you expect
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've found logging useful when importing markov builder into other projects. It can be help when debugging code in those projects and I think it can be disabled and the levels can be set externally too. Without this, I might have to have a local version of Markov builder and be editing in print statements.
Not too bothered, but I think it'd be convenient (for me at least) to keep them in.
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 were going very "proper" software dev here, then you'd say you want a "separation of concerns" which says that you would never want to know about markov builder internals from another project, because (1) there shouldn't be any bugs (full unit tests) and (2) the API and documentation should guide your use of the markov builder, not the implementation of that API
For this particular example, I think you want either
- An exception, because this shouldn't happen so the program should stop, or
- An exception, which the user code should catch and use to update whatever needs updating (I don't think this is likely here), or
- A warning (
warnings.warn('This thing happened but code will keep running anyway'))
A typical use case for a warning would be something like you're reading a file and it's in a newer format than was tested for so you let the user know that it will probably work but is untested. Or you're doing some fancy matrix thing that isn't 100% necessary and it fails so you can continue with an alternative strategy.
In this case I'm pretty sure you want an exception: The user's code is wrong or else this shouldn't happen, so stop
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.
In particular cause it's a TypeError you're catching here. So its looks like it definitely the user doing it wrong, not markov builder, so you want to say "I can't guarantee the output, because this needs to fixed on your side"?
Worth even testing that they're both set before calling numpy?
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 should have mentioned - this function has been changed now to raise a ValueError in this case.
markov-builder/markov_builder/MarkovChain.py
Line 518 in fb54457
| def get_equilibrium_distribution(self, param_dict: dict = {}) -> Tuple[List[str], np.array]: |
def get_equilibrium_distribution(self, param_dict: dict = {}) -> Tuple[List[str], np.array]:
"""Compute the equilibrium distribution of the CTMC for the specified transition rate values
:param param_dict: A dictionary specifying the values of each transition rate
:return: A 2-tuple describing equilibrium distribution and labels defines which entry relates to which state
:raises ValueError: If not every necessary parameter is defined in param_dict
"""
A, B = self.eliminate_state_from_transition_matrix(use_parameters=True)
labels = self.graph.nodes()
vars_used = [*A.free_symbols, *B.free_symbols]
for _var in vars_used:
if str(_var) not in param_dict:
raise ValueError(f"Error evaluating equilibrium distribution {_var}\n"
f"A={A}\nB={B}\nparams={param_dict}\n")
ss = -np.array(A.LUsolve(B).evalf(subs=param_dict)).astype(np.float64)
ss = np.append(ss, 1 - ss.sum())
return labels, ss
I think there was a possibility of a TypeError if you didn't provide a value for some of the parameters. In that case, it tries to evaluate a sympy Symbol as a float.
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.
But there are still some debug messages elsewhere
Description
Add models 1-11 and 30 from CardiacModelling/hergModels/tree/master/models_myokit. These models are tested by comparing Myokit Simulation output form the original .mmt files with those generated through the Markov_builder.
The newly added models make extensive use of the shared_variables_dict. The behaviour of parameterise_rates was modified to make it more convenient to set these variables to numerical values.
These changes exposed some issues with reversibility checking and transition matrix calculation which have now been fixed.
The new models can be easily generated by users and modified to include drug-trapping or additional states/parameters.
Types of changes
Testing
Documentation checklist