10
$\begingroup$

A functional square root of a function $g$ is another function $f$ such that $g=f\circ f$. According to that article, there is a systematic approach to finding a functional square root that involves solving Schröder's equation though I don't know what procedure to apply.

For example, the functional square root of $x/(2 - x)$ is $x/(\sqrt{2} + x - x\sqrt{2})$ and we can verify:

f[x_] := x/(Sqrt[2] + x - x*Sqrt[2])
FullSimplify[f[f[x]]]
(* returns: x/(2 - x) *)

I'd like to know how to go about finding exact or approximate functional square roots in Mathematica. Suppose I want to find an $f$ for $g(x)=\cos(4 \pi x)$. I try to frame it as a differential equation but this is unsuccessful. I am not even sure what the initial conditions should be either:

D[f[f[x]], x]
(* Derivative[1][f][x] Derivative[1][f][f[x]] *)

D[Cos[4 Pi x], x]
(* -4 Pi Sin[4 Pi x] *)

NDSolve[{-4 Pi Sin[4 Pi x] == Derivative[1][f][x] Derivative[1][f][f[x]], f[0] == 1}, f, {x, 0, 1}]
(* Power::infy: Infinite expression 1/0 encountered. *)

Apparently there are power series methods that can solve problems like this, and these things called Carleman Matrices. I looked around in the docs and found CarlemanLinearize but I can't tell if this is related to this problem.

@J.M. has a function to construct a Carleman matrix here so I will take a look at that.


I've tried to follow along with this answer here but my coefficients end up as complex numbers and the plot doesn't really look like iterating twice will remotely resemble a cosine:

x0 = 0; n = 30;(*expansion point and order*)
cosCM = N[CarlemanMatrix[Cos[4 Pi x], {x, x0, n}], 30];
shalfCoeffs = MatrixPower[Transpose[cosCM], 1/2, UnitVector[n + 1, 2]];
shalf[x_] = Fold[(#1 x + #2) &, 0, Reverse[shalfCoeffs]];
ReImPlot[shalf[x], {x, 0, 1}]
$\endgroup$
5
  • $\begingroup$ The equation $$-4 \pi \sin (4 \pi x)=f'(x) f'(f(x)) $$ is not an ODE (e.g. see en.wikipedia.org/wiki/Ordinary_differential_equation ). $\endgroup$ – user64494 Aug 3 '20 at 5:36
  • 1
    $\begingroup$ I presume you have already seen this? $\endgroup$ – J. M.'s ennui Aug 4 '20 at 1:18
  • 1
    $\begingroup$ @J.M. I have to say that this post on mathoverflow is extremely confusing. Just a set of dispersed radically different answers. I would really be great if someone can elaborate. As of now, I do believe that the problem is mathematically and numerically open. $\endgroup$ – yarchik Aug 4 '20 at 15:48
  • $\begingroup$ @yarchik, I think the answer that mentioned that the coefficients of the semi-iterate are complex was interesting, at the very least. $\endgroup$ – J. M.'s ennui Aug 5 '20 at 14:47
  • $\begingroup$ I'm wondering if a neural network approach would work, but rather than back-propagate the error of net(x) to sin(x), instead backpropagate the error of net(net(x)) to sin(x). $\endgroup$ – flinty Aug 5 '20 at 15:45
3
$\begingroup$

I was not able to get a half iterate for $\cos(...)$ anything, and from reading around a bit it appears that half iterates of $\cos$ might be impossible either due to convergence or the evenness of $\cos$'s series expansion terms. However, I was able to get a half-iterate for a small part of the domain of $\sin(4 \pi x)$ through fixed point iteration on the series, though it becomes inaccurate quite quickly:

(* Try to find a half iterate of Sin[4 \[Pi] x] *)
halfit[x_] = Nest[(Sin[4 \[Pi]*Normal[InverseSeries[Series[#, {x, 0, 6}]]]] + #)/2 &, x, 8];

Plot[{halfit[halfit[x]], Sin[4 \[Pi] x]}, {x, -\[Pi]/2, \[Pi]/2}, 
 PlotRange -> {-1, 1}, 
 PlotStyle -> {Directive[Thick, Red], Directive[Blue]}]

half iterate approx

I was able to get an approximation of the half-sine by a different method using a Newton series, though this does not work for a higher frequency sine like $\sin(4 \pi x)$ and produces a very noisy function. The resulting $\mathrm{hsin}(\mathrm{hsin}(x))\approx\sin(x)$ is not too bad an approximation judging by the plot:

newtonfhalf[f_, x_, mmax_] := 
 Sum[Binomial[1/2, m] Sum[
    Binomial[m, k] (-1)^(m - k) Nest[f, x, k], {k, 0, m}], {m, 0, mmax}]

nth = Function[{x}, newtonfhalf[Sin[#] &, x, 40]];
nthh2 = nth[nth[x]];
Plot[{Sin[x], nthh2}, {x, -4, 4}, 
 PlotStyle -> {Directive[Thick, Blue], Directive[Red]}]

half sine newton


I've had some luck with a neural network approach to the problem. I've found it's possible to train a network in a non-standard way to find an approximate half-iterate. Assume a network $N$ of 1 input and 1 output node with arbitrary layers in between and that we're trying to find a half-iterate for the function $\mathrm{target}(x)$:

  1. Run $N$ forward on a random input $x_i$ and generate output $y_i$
  2. Run $N$ forward again using $y_i$ as input, generating output $y_i'$
  3. The loss is $(\mathrm{target}(x_i) - y_i')^2$. Back-propagate and update $N$ and return to step 1.

The resulting network is hopefully trained such that $N(N(x)) \approx \mathrm{target}(x)$.

I wasn't sure how to approach this in Mathematica but this is my first time using PyTorch ever, so what follows may be a bit basic:

import torch
import torch.nn as nn
import torch.optim as optim

from math import pi, sin, cos
import random
import csv


def targetfn(x):
    return sin(x)


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.lin = nn.Linear(1, 20)
        self.lmid1 = nn.Tanh()
        self.lmid2 = nn.Linear(20, 20)
        self.lmid3 = nn.Tanh()
        self.lout = nn.Linear(20, 1)

    def forward(self, w):
        w = self.lin(w)
        w = self.lmid1(w)
        w = self.lmid2(w)
        w = self.lmid3(w)
        return self.lout(w)


def train():
    net = Net()
    print(net)

    optimizer = optim.SGD(net.parameters(), lr=0.01)
    criterion = nn.MSELoss()

    # init random
    net.zero_grad()
    outinit = net(torch.randn(1))
    outinit.backward(torch.randn(1))

    for i in range(100000):
        x = random.uniform(-2 * pi, 2 * pi)
        target = torch.tensor([targetfn(x)])
        y1 = net(torch.tensor([x]))
        net.zero_grad()
        optimizer.zero_grad()
        y2 = net(y1)
        loss = criterion(y2, target)
        loss.backward()
        optimizer.step()

    return net


def main():
    net = train()

    with open("hfn.csv", 'w', newline='') as csvfile:
        csvwriter = csv.writer(csvfile, delimiter=',')
        n = 2000
        xmin = -2 * pi
        xmax = 2 * pi
        step = (xmax - xmin) / n
        x = xmin
        for i in range(n):
            csvwriter.writerow([x, net(torch.tensor([x])).item()])
            x += step


if __name__ == '__main__':
    main()

... and plotting in Mathematica:

data = Import["hfn.csv"];
intp = Interpolation[data];
Plot[{Sin[t], intp[intp[t]]}, {t, -2 \[Pi], 2 \[Pi]}, 
 PlotRange -> {-1.3, 1.3}, 
 PlotStyle -> {Directive[Thick, Blue], Directive[Thin, Red]}, 
 PlotTheme -> "Scientific"]

pytorch half sin

This is looking good for $\sin(x)$. What about $\cos(x)$? I changed targetfn in the python code above and at least I got something that looked close to a cosine wave:

half cosine approximation

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.