Week 4 and the final week of the first phase of the Coding Period is coming to an end. The work, which was initially proposed to be done in FormalPowerSeries has been done on a term-by-term level, i.e. the operations which have been implemented return truncated terms of the resultant FormalPowerSeries, instead of returning a FormalPowerSeries object. Sartaj and I have already discussed about the class implementation, in a recent meeting. Here are the deliverables which has been completed this week :–

  • The PR #17017 consisted of a convolve function, which takes in a order n term, and returns the truncated terms of the resultant convoluted formal power series. The API of the convolve function was in line with the convolution function in sympy.discrete.convolutions. More specifically, it was –
       def convolve(self, other, x=None, n=6, cycle=0, dyadic=False, subset=False):
       """The logic of the code"""
    

    Sartaj suggested that there should be a method kwarg, which can take in which method of convolution the user wants to apply, instead of passing various kwargs representing the various methods. So I changed the API of the function, in lines with the above comment. Currently it is –

       def convolve(self, other, x=None, n=6, cycle=0, method=None):
       """The logic of the code"""
    

    in which method will be one of [dyadic, subset, None], and cycle will represent the no. of cycles in cyclic convolution. Docs have also been updated. The PR is now in a mergeable state !!

  • I also opened a [PR #17064], which deals with the composition and inversion of formal power series. I had implemented a CoeffBell class, where essentially CoeffBellCompose(f, n, k) represents Bell polynomials of the second kind. Note that both n and k should be integers. The implementation of the eval function of the CoeffBell class is as follows –
    @classmethod
    def eval(cls, f, n, k):
    fp = fps(f)
    kv = fp.ak.variables[0]
    i = symbols('i')
    
    if n.is_integer and k.is_integer:
        fp_seq = sequence(fp.ak.formula, (kv, 1, n-k+1))
        fact_seq = sequence(factorial(i), (i, 1, n-k+1))
        bell_seq = fp_seq * fact_seq
        return bell(n, k, tuple(bell_seq))
    

    Then we can easily define bell sequences in terms of CoeffBell classes. Once we define them, all other terms can defined as sequences, and then the sequences can be multiplied and then the terms can be added up. Once we implement them, we can add up the sequence terms, and get the desired resultant truncated terms.

The various tests implemented here will give you an idea. They are :-

def test_fps__composition():
    f1, f2, f3 = fps(exp(x)), fps(sin(x)), fps(cos(x))

    raises(ValueError, lambda: f1.compose(sin(x), x))
    raises(ValueError, lambda: f1.compose(fps(sin(x), dir=-1), x, 4))
    raises(ValueError, lambda: f1.compose(fps(sin(x), x0=1), x, 4))
    raises(ValueError, lambda: f1.compose(fps(sin(y)), x, 4))

    raises(ValueError, lambda: f1.compose(f3, x))
    raises(ValueError, lambda: f2.compose(f3, x))

    assert f1.compose(f2, x) == 1 + x + x**2/2 - x**4/8 - x**5/15 + O(x**6)
    assert f1.compose(f2, x, n=4) == 1 + x + x**2/2 + O(x**4)
    assert f1.compose(f2, x, n=8) == \
        1 + x + x**2/2 - x**4/8 - x**5/15 - x**6/240 + x**7/90 + O(x**8)

    assert f2.compose(f2, x, n=4) == x - x**3/6 + O(x**4)
    assert f2.compose(f2, x, n=8) == x - x**3/6 + 11*x**5/120 - 127*x**7/5040 + O(x**8)


def test_fps__inverse():
    f1, f2, f3 = fps(sin(x)), fps(exp(x)), fps(cos(x))

    raises(ValueError, lambda: f1.inverse(x))
    raises(ValueError, lambda: f1.inverse(x, n=8))

    assert f2.inverse(x) == 1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + O(x**6)
    assert f2.inverse(x, n=8) == \
        1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + x**6/720 - x**7/5040 + O(x**8)

    assert f3.inverse(x) == 1 + x**2/2 + 5*x**4/24 + O(x**6)
    assert f3.inverse(x, n=8) == 1 + x**2/2 + 5*x**4/24 + 61*x**6/720 + O(x**8)
  • Now that these implementations are over with, I have to start thinking about how we can make FormalPowerSeries objects as a result of these functions, instead of returning only the truncated terms. The first step in doing that would be to make all the sequences infinite, and save whatever sequence can be saved. Then we can create a sub-class out of FormalPowerSeries, which can have a custom _eval_terms function which, instead of calculating term by term, will calculate the addition of all terms together. Once we have the base sub-class ready, we can then move forward with creating three sub-classes out of the sub-class, one each for convolve, compose, and inverse. I will publish a PR, consisting of these changes, by the end of this week.

The first phase is over !! Phew !! See you in the next week. Adios till then !!