Phase 2 of the coding period has started. This week has gone in wrapping up the left-over work of FormalPowerSeries
. I had a meeting with Sartaj on Tuesday 25th of June, about the work left to be done on FormalPowerSeries
module. We agreed that some minor changes need to be done on the PRs that have already been published. And also the class PR needs to be published. Here are the deliverables that have been completed this week.
- PR #17011, which returns the connvoluted formal power series has been refactored. According to Kalevi’s comment here, and Sartaj’s follow-up discussion here, we felt that all the various forms of convolutions are not really that applicable to formal power series, backed by the evidence that there is no prior literature describing these operations for power series.
Thus I restricted the function to only the linear infinite cycle case, and removed all other methods of convolution. The function name was changed to
product
, and now it returns something like this –
>>> from sympy import fps, sin, exp, convolution
>>> from sympy.abc import x
>>> f1 = fps(sin(x))
>>> f2 = fps(exp(x))
>>> f1.product(f2, x, 4)
x + x**2 + x**3/3 + O(x**4)
- PR #17064, which was published to create
compose
andinverse
functions, were changed a bit. Initially, all the sequences were being created and used in the functions, which were not at all optimal. After Sartaj’s valuable suggestions, I created all the possibe sequences in the__init__
function of theFormalPowerSeries
class itself.
def __init__(self, *args):
ak = args[4][0]
k = ak.variables[0]
self.ak_seq = sequence(ak.formula, (k, 1, oo))
self.fact_seq = sequence(factorial(k), (k, 1, oo))
self.bell_coeff_seq = self.ak_seq * self.fact_seq
self.sign_seq = sequence((-1, 1), (k, 1, oo))
As we can see here, fact_seq
, which is a factorial sequence, sign_seq
, which is a sign alternating sequence, and a custom bell_coeff_seq
have been defined in __init__
. Since sequences are lazy, defining them at object creation time won’t affect performance.
Also, the code in bothe the compose
and inverse
required some optimization changes. More can be understood by following the comments and commits in the PR.
- Till now, all the operations have been returning truncated terms. The problem is, it breaks the functionality of the FormalPowerSeries class, i.e a function defined in the
FormalPowerSeries
class should return a related object. But the problem with that was, it was very difficult to represent the coefficient sequence of the resultant composed or inverted formal power series. So, after a huge amount of discussion, I decided to create aFiniteFormalPowerSeries
class, which will actually return a list of the coefficients of the coefficient sequence, based on the ordern
which the user provides.
Based on that, I creates a PR #17134. This PR consists of the implementation of FiniteFormalPowerSeries
class, in which the coefficient sequence is a list of real numbers, computed based on the algorithm of the function calling it. Presently, compose
and inverse
, which are functions of FormalPowerSeries
class, return a FiniteFormalPowerSeries
object.
An example would be –
>>> from sympy import *
>>> x = symbols('x')
>>> f1, f2 = fps(exp(x)), fps(sin(x))
>>> f1.compose(f2)
FiniteFormalPowerSeries(exp(sin(x)), x, 0, 1, ([1, 1/2, 0, -1/8, -1/15], SeqFormula(x**_k, (_k, 0, oo)), 1))
>>> f1.compose(f2).truncate(6)
1 + x + x**2/2 - x**4/8 - x**5/15 + O(x**6)
>>> f1.inverse()
FiniteFormalPowerSeries(exp(-x), x, 0, 1, ([-1, 1/2, -1/6, 1/24, -1/120], SeqFormula(x**_k, (_k, 0, oo)), 1))
>>> f1.inverse().truncate(6)
1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + O(x**6)
The implementation of the FiniteFormalPowerSeries
is somewhat like this :-
class FiniteFormalPowerSeries(FormalPowerSeries):
def __new__(cls, *args):
args = map(sympify, args)
return Expr.__new__(cls, *args)
def __init__(self, *args):
pass
def _eval_term(self, pt):
try:
pt_xk = self.xk.coeff(pt)
pt_ak = S.Zero
if pt is not S.Zero:
pt_ak = self.ak[pt-1] # Simplify the coefficients
except IndexError:
term = S.Zero
else:
term = (pt_ak * pt_xk)
if self.ind:
ind = S.Zero
for t in Add.make_args(self.ind):
pow_x = self._get_pow_x(t)
if pt == 0 and pow_x < 1:
ind += t
elif pow_x >= pt and pow_x < pt + 1:
ind += t
term += ind
return term.collect(self.x)
def polynomial(self, n=6):
"""Truncated series as polynomial.
Returns series expansion of ``f`` upto order ``O(x**n)``
as a polynomial(without ``O`` term).
"""
if (n > len(self.ak)+1):
raise ValueError("The order of truncate should be less than or equal to the"
"order specified in the compose function.")
terms = []
for i, t in enumerate(self):
xp = self._get_pow_x(t)
if xp >= n:
break
elif xp.is_integer is True and i == n + 1:
break
elif t is not S.Zero:
terms.append(t)
return Add(*terms)
As you can see, only the _eval_term
and the polynomial
functions have been overrriden, as compared to the FormalPowerSeries
, from which it is inherited. Documentation and tests remain to be added, so have been added in the TODO section.
That’s it for Week 5 !! See you in the next week !!