4

I'm trying to use Sage in Anaconda 3 but it looks that the libraries are not imported.

I firstly created a new environment 'ipykernel_py2' and then installed Python 2 as explained in here. With this I can have both Python 3 and Python 3 up and running in Anaconda 3.

Then I went to the kernel's folder created (C:\Users\YOUR_USERNAME\AppData\Local\Continuum\anaconda3\envs\ipykernel_py2\share\jupyter\kernels) and pasted Sage's kernel (taken from C:\Program Files\SageMath 8.2\runtime\opt\sagemath-8.2\local\share\jupyter\kernels). This allows to create new SageMath files in Jupyter but the kernel is dead. To activate the kernel I used Anaconda Prompt and typed:

activate ipykernel_py2
python -m ipykernel install --user --name sagemath --display-name "SageMath 8.2"

So the kernel is now activated and I can create and run Sage files. However the libraries are still not working. It seems that the file is running like a normal Python 2 file.

Does anyone know how to fix this? Do I need to create a seperate environment?

J. Serra
  • 440
  • 1
  • 4
  • 13

1 Answers1

4

Sage for Windows runs under a UNIX emulation environment called Cygwin. Looking at the sagemath/kernel.json it contains:

{"display_name": "SageMath 8.2", "argv": ["/opt/sagemath-8.2/local/bin/sage", "--python", "-m", "sage.repl.ipython_kernel", "-f", "{connection_file}"]}

You can see here that it has a UNIX-style path to the sage executable. This path only makes sense to other programs running under Sage's Cygwin environment, and is meaningless to native Windows programs. Simply converting it to the equivalent Windows path won't work either, because bin/sage is actually a shell script. At the very least you need to provide a Windows path to the bash that comes with Cygwin and pass it the UNIX path to the sage executable (the same as the one above). Without a login shell, most environment variables needed won't be set either, so you probably need bash -l.

So, something like:

{"display_name": "SageMath 8.2", "argv": ["C:\\Program Files\\SageMath 8.2\\runtime\\bin\\bash.exe", "-l", "/opt/sagemath-8.2/local/bin/sage", "--python", "-m", "sage.repl.ipython_kernel", "-f", "{connection_file}"]}

might work. The one thing I'm not sure about is whether the {connection_file} argument will be handled properly either. I haven't tested it.

Update: Indeed, the above partially works, but there are a few problems: The {connection_file} argument as passed as the absolute Windows path to the file. While Cygwin can normally translate transparently from Windows paths to a corresponding UNIX path, there is a known issue that Python's os.path module on Cygwin does not handle Windows-style paths well, and this leads to issues.

The other major problem I encountered was that IPKernelApp, the class that drives generic Jupyter kernels, has a thread which polls to see if the kernel's parent process (in this case the notebook server) has exited, so it can appropriately shut down if the parent shuts down. This is how kernels know to automatically shut down when you kill the notebook server.

How this is done is very different depending on the platform--Windows versus UNIX-like. Because Sage's kernel runs in Cygwin, it chooses the UNIX-like poller. However, this is wrong if the notebook server happens to be a native Windows process, as is the case when running the Sage kernel in a Windows-native Jupyter. Remarkably, the parent poller for Windows can work just as well on Cygwin since it accesses the Windows API through ctypes. Therefore, this can be worked around by providing a wrapper to IPKernelApp that forces uses of ParentPollerWindows.

A possible solution then looks something like this: From within the SageMath Shell do:

$ cd "$SAGE_LOCAL"
$ mkdir -p ./share/jupyter/kernels/sagemath
$ cd ./share/jupyter/kernels/sagemath
$ cat <<_EOF_ > kernel-wrapper.sh
#!/bin/sh
here="$(dirname "$0")"
connection_file="$(cygpath -u -a "$1")"
exec /opt/sagemath-8.2/local/bin/sage --python "${here}/kernel-wrapper.py" -f "${connection_file}"
_EOF_
$ cat <<_EOF_ > kernel-wrapper.py
from ipykernel.kernelapp import IPKernelApp as OrigIPKernelApp
from ipykernel.parentpoller import ParentPollerWindows
from sage.repl.ipython_kernel.kernel import SageKernel


class IPKernelApp(OrigIPKernelApp):
    """
    Although this kernel runs under Cygwin, its parent is a native Windows
    process, so we force use of the ParentPollerWindows.
    """

    def init_poller(self):
        if self.interrupt or self.parent_handle:
            self.poller = ParentPollerWindows(self.interrupt,
                                              self.parent_handle)


IPKernelApp.launch_instance(kernel_class=SageKernel)
_EOF_

Now edit the kernel.json (in its existing location under share\jupyter\kernels\sagemath) to read:

{"display_name": "SageMath 8.2", "argv": ["C:\\Program Files\\SageMath 8.2\\runtime\\bin\\bash.exe", "-l", "/opt/sagemath-8.2/local/share/jupyter/kernels/sagemath/kernel-wrapper.sh", "{connection_file}"]}

This runs kernel-wrapper.sh which in turn runs kernel-wrapper.py. (There are a few simplifications I could make to get rid of the need for kernel-wrapper.sh completely, but that would be easier in SageMath 8.3 which includes PyCygwin.)

Make sure to change every "8.2" to the appropriate "X.Y" version for your Sage installation.

Update: Made some updates thanks to feedback from a user, but I haven't tested these changes yet, so please make sure instead of blindly copy/pasting that every file/directory path in my instructions exists and looks correct.

As you can see, this was not trivial, and was never by design meant to be possible. But it can be done. Once the kernel itself is up and running it's just a matter of talking to it over TCP/IP sockets so there's not too much magic involved after that. I believe there are some small improvements that could be made on both the Jupyter side and on the Sage side that would facilitate this sort of thing in the future...

Iguananaut
  • 21,810
  • 5
  • 50
  • 63
  • Out of curiosity, I tried it, and it *almost* works except, as I feared, for the `{connection_file}` being passed correctly. Let me see if I can't think of a workaround for that... – Iguananaut Jul 12 '18 at 15:17
  • I was able to put a wrapper script around the whole thing in order to deal with mapping the connection file filename, and that seems to work. However, now there appears to be another problem in that the notebook appears to think the kernel is dead even if it isn't. Which is interesting because if I just run the kernel directly myself (with a manual connection file name) and then pass commands to it with `jupyter run` it works. So perhaps there's some other confusion concerning process management... – Iguananaut Jul 12 '18 at 16:49
  • Thanks for your detailed ansewr @Iguananaut. Unfortunately I'm afraid I cannot implement it as I'm a basic Python user and my programming skills are limited. It looks like there is a way to do it (as you explained) but far to cumbersome for basic users. I'll keep using my SageMath shell as before. – J. Serra Jul 13 '18 at 15:27
  • I'd suggest giving it a try anyways. I gave you pretty much exactly the commands to run, and if you do it should just work. Note, sage also comes with the notebook... – Iguananaut Jul 13 '18 at 17:32
  • @Iguananaut I hope you are still around. I am trying to do what you said above in order to help out a student. Unfortunately, I myself am not accustomed to all this because I personally don't use Windows. When trying to create "kernel-wrapper.sh", I get an error "cygpath can't convert empty path" and indeed, the line in the file reads connection_file="" Do you happen to know what I need to do here? I guess I need to set some path, but which and how? – MWL Mar 14 '22 at 07:57