Skip to content

refactor euler solvers#21692

Merged
oscarbenjamin merged 10 commits into
sympy:masterfrom
Mohitbalwani26:refactor_euler
Jul 4, 2021
Merged

refactor euler solvers#21692
oscarbenjamin merged 10 commits into
sympy:masterfrom
Mohitbalwani26:refactor_euler

Conversation

@Mohitbalwani26

Copy link
Copy Markdown
Member

References to other Issues or PRs

SEE #18348

Brief description of what is fixed or changed

Refactor Euler solvers and cleanup the constant coff code from ode.py

Other comments

Release Notes

NO ENTRY

@sympy-bot

Copy link
Copy Markdown

Hi, I am the SymPy bot (v161). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

  • No release notes entry will be added for this pull request.
Click here to see the pull request description that was parsed.
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
SEE #18348 

#### Brief description of what is fixed or changed
Refactor Euler solvers and cleanup the constant coff code from ode.py

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers.

or if no release note(s) should be included use:

NO ENTRY

See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
information on how to write release notes. The bot will check your release
notes automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
NO ENTRY
<!-- END RELEASE NOTES -->

Comment thread sympy/solvers/ode/tests/test_ode.py Outdated
def test_undetermined_coefficients_match():
assert _undetermined_coefficients_match(g(x), x) == {'test': False}
assert _undetermined_coefficients_match(sin(2*x + sqrt(5)), x) == \
F = lambda eq: NthLinearConstantCoeffUndeterminedCoefficients(SingleODEProblem(eq, f(x), x))._undetermined_coefficients_match(eq,x)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's helpful during refactoring if the tests are changed as little as possible so we can be clear that the mostly unchanged tests still pass. This could be:

def _undetermined_coefficients_match(eq, x):
    problem = SingleODEProblem(eq, f(x), x)
    solver = NthLinearConstantCoeffUndeterminedCoefficients(problem)
    return solver. _undetermined_coefficients_match(eq, x)

Then the rest of the testing code would not need to be changed.

Comment thread sympy/solvers/ode/single.py Outdated
>>> f = Function('f')
>>> eq = 9*x*exp(x) + exp(-x)
>>> obj = NthLinearConstantCoeffUndeterminedCoefficients(SingleODEProblem(eq,f(x),x))
>>> obj._undetermined_coefficients_match(eq,x)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There should be a space after a comma (here and everywhere else)

Comment thread sympy/solvers/ode/single.py Outdated
>>> from sympy import log, exp
>>> from sympy.solvers.ode.ode import _undetermined_coefficients_match
>>> from sympy import log, exp, Function
>>> from sympy.solvers.ode.single import NthLinearConstantCoeffUndeterminedCoefficients,SingleODEProblem

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This line is too long

Comment thread sympy/solvers/ode/single.py Outdated
>>> eq = log(x)
>>> obj = NthLinearConstantCoeffUndeterminedCoefficients(SingleODEProblem(eq,f(x),x))
>>> obj._undetermined_coefficients_match(eq,x)
{'test': False}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's probably better to give an example using dsolve here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This docstring is just for getting trial set method. For examples of undet_coeff solver the docstring at class level has examples with dsolve.

Comment thread sympy/solvers/ode/single.py Outdated
sol = collect(sol, x**i*exp(reroot*x))
sol = powsimp(sol)
return [Eq(f(x), sol)]
return Eq(f(x), sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Returning anything other than a list should fail here. Is there some code somewhere else that checks for this? Why didn't this fail before?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Currently test_single.py handles both type of solutions with list as well as without as some of the solvers are yet to be refactored. After refactoring I will remove the conditional part from test suite so that it should expect list of solution from every solver.

return gensols


def _get_general_solution(self, *, simplify_flag: bool = True):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Only one blank line here

Comment thread sympy/solvers/ode/single.py Outdated

def _get_general_solution(self, *, simplify_flag: bool = True):
self._get_sols(self.r)
return [self.gsol]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this just be return self._get_sols()?

It would be better if the methods in these classes don't use internal variables like this. We should minimise instance attributes.

Why is _get_sols a different method from _get_general_solution?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

_get_sols() is the method which calcutates the roots of characteristic equation. And to form that we need match_object.
self.r was kept as an argument because this method is also used by euler variation of parameters and euler undetermined coeffs so we need to pass their match object to this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than passing the match object it would be better if this had names arguments like roots, trialset or something.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why can't the vop and undet classes just use the euler class get_general_solution method?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Why can't the vop and undet classes just use the euler class get_general_solution method?

because get_general_solution will return a equation where as in vop and undet we just want the list of roots of characterstic equation.

Comment thread sympy/solvers/ode/single.py Outdated
chareq, symbol = S.Zero, Dummy('x')

for i in r.keys():
if not isinstance(i, str) and i >= 0:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If r is a dict then it isn't necessary to call keys here (the dict is already iterable).

Why would i be a str?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

r can have key named 'trialset' which will be used for undetermined coefficient.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It would be better to store trialset as a separate attribute rather than mix it up with the roots:

self.roots = ...
self.trialset = ...

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

Side note: these Euler solvers take up 400 lines that would be reduced to almost nothing if the ODE was simply solved by substitution.

Comment thread sympy/solvers/ode/single.py Outdated
fx = self.ode_problem.func
x = self.ode_problem.sym
(C1,) = self.ode_problem.get_numbered_constants(num=1)
(C1, ) = self.ode_problem.get_numbered_constants(num=1)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This space should not be here

Comment thread sympy/solvers/ode/single.py Outdated

These kinds of differential equations can be solved in a general way. The
integrating factor `e^{\int P(x) \,dx}` will turn the equation into a
integrating factor `e^{\int P(x) \, dx}` will turn the equation into a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The \, in latex is different from other commas since it represents a small space. I don't think that we need to also add a space after this.

Comment thread sympy/solvers/ode/single.py Outdated

.. math:: y' + F\left(\!\frac{a_1 x + b_1 y + c_1}{a_2 x + b_2 y +
c_2}\!\right) = 0\text{,}
c_2}\!\right) = 0\text{, }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This space should not be here

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

It looks like you've done a find and replace to add spaces after commas but some of these places shouldn't be changed so can you check through those?

@Mohitbalwani26

Copy link
Copy Markdown
Member Author

It looks like you've done a find and replace to add spaces after commas but some of these places shouldn't be changed so can you check through those?

Yes I did that. I will check once. Also I am trying to move all the reusable functions to another file so that classes look minimal and kind of wrapper for them.

@sympy-bot

sympy-bot commented Jul 2, 2021

Copy link
Copy Markdown

🟠

Hi, I am the SymPy bot (v161). I've noticed that some of your commits add or delete files. Since this is sometimes done unintentionally, I wanted to alert you about it.

This is an experimental feature of SymPy Bot. If you have any feedback on it, please comment at sympy/sympy-bot#75.

The following commits add new files:

  • 2eb490e:
    • sympy/solvers/ode/euler_nonhomogen_helpers.py

If these files were added/deleted on purpose, you can ignore this message.

@Mohitbalwani26

Copy link
Copy Markdown
Member Author

@oscarbenjamin does this way seem good?
Also if yes then what should be the name of euler_nonhomogen_helpers.py?

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

@oscarbenjamin does this way seem good?
Also if yes then what should be the name of euler_nonhomogen_helpers.py?

Yes, I think so. I think it should just be called nonhomogeneous.py. Other functions like variation of parameters etc can go there later.

@Mohitbalwani26

Copy link
Copy Markdown
Member Author

Yes, I think so. I think it should just be called nonhomogeneous.py. Other functions like variation of parameters etc can go there later.

So should I again move functions like solve_variation_of_param , solve_unde_coeff back to classes?

Currently all the functions which are there in new file they all are used by more than 1 solver. If we again keep any of them into solver, we will have to create instance of that class into another.

So what all functions should I keep in classes back?

gensols.append(sin_form)
return gsol, gensols

def _solve_variation_of_parameters(eq, func, roots, homogen_sol, order, match_obj, simplify_flag=True):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two blank lines

Comment thread sympy/solvers/ode/nonhomogeneous.py Outdated
psol = trigsimp(psol, deep=True)
return Eq(f(x), homogen_sol.rhs + psol)

def _get_const_characterstic_eq_sols(r, func, order):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

characteristic

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

So should I again move functions like solve_variation_of_param , solve_unde_coeff back to classes?

No I think this is fine. Eventually these functions should be made into something that it is more usefully reusable though.

@@ -0,0 +1,463 @@
from collections import defaultdict

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There should be some kind of comment or module level docstring at the top of the module explaining what it is.

Comment thread sympy/solvers/ode/nonhomogeneous.py Outdated
return False


def _get_euler_characterstic_eq_sols(eq, func, match_obj):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The functions in this file should have at least a one line docstring explaining what they are as well as comments explaining why they are here and what they are used for.

def _get_euler_characteristic_eq_sols(eq, func, match_obj):
r"""
It returns the solution of homogeneous part of the linear euler ODE and
the list of roots of characteristic equation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don't use "It" here. Just "Returns ..." is fine.

This doesn't really explain the function very well. What is the match_obj parameter supposed to be for example?

Comment thread sympy/solvers/ode/nonhomogeneous.py Outdated
Functions that are for internal use:

1) _test_term(coeff, func, order) - It takes coefficient of partiular term, function and
order to which it is associated. It returns whether given term matches with linear euler ODEs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think that you need to detail each function at the top here. These kinds of comments/description are better placed in or immediately before the function.

This module level docstring just needs to explain the general purpose of the module.

def _wilds(self, f, x, order):
fy = Wild('fy', exclude=[0, f(x).diff(x), f(x).diff(x, 2)])
return (fy,)
return (fy, )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There shouldn't be a space here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Okay, will remove it in next PR.

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

Okay, looks good.

@oscarbenjamin
oscarbenjamin merged commit eed6198 into sympy:master Jul 4, 2021
@Mohitbalwani26
Mohitbalwani26 deleted the refactor_euler branch July 5, 2021 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants