Introduction to Jupyter Notebook¶

A Jupyter notebook combines executable code, formatted explanations, equations, figures, and results in one document. This makes notebooks useful for exploring a problem and documenting a scientific calculation.

A notebook has two distinct parts:

  • the .ipynb file, which stores cells, metadata, and optionally their saved outputs;
  • a kernel, which is a running Python process that executes the code.

The notebook document can remain open after its kernel has stopped, and closing a browser tab does not necessarily stop the kernel.

Code cells and Markdown cells¶

The two cell types used most often are:

  • Code cells, which are sent to the kernel for execution;
  • Markdown cells, which contain formatted text, links, equations, and images.

This is a Markdown cell. Markdown supports:

  • italic and bold text;
  • numbered and bulleted lists;
  • inline code such as np.linspace;
  • links;
  • inline mathematics, such as $E=mc^2$;
  • displayed mathematics:

\begin{equation} \int_0^\infty e^{-x}\,dx=1. \end{equation}

An image copied from Preview can be pasted directly into a Markdown cell. Jupyter stores it as an attachment inside the notebook.

Command mode and edit mode¶

A selected cell can be in one of two modes:

  • Edit mode: type inside the cell. Press Enter or double-click a cell to enter edit mode.
  • Command mode: operate on the cell as a whole. Press Esc to enter command mode.

Common execution shortcuts are:

  • Shift-Enter: run the cell and select the next cell;
  • Ctrl-Enter: run the cell and remain on it;
  • Alt-Enter: run the cell and insert a new cell below.

Common command-mode shortcuts are:

  • a / b: insert a cell above / below;
  • m / y: change the cell to Markdown / code;
  • c, x, v: copy, cut, and paste cells;
  • dd: delete a cell;
  • z: undo deletion of a cell.

Shortcuts can differ slightly between Jupyter Notebook and JupyterLab. Use the Command Palette or the keyboard-shortcuts menu to see the authoritative list for the interface you are running.

Executing Python¶

Only the final unassigned expression in a code cell is displayed automatically. Use print or display when you want to show several results.

In [1]:
import numpy as np

print("cos(pi) =", np.cos(np.pi))
print("2 pi =", 2*np.pi)

np.zeros(5)  # The final expression is displayed automatically.
cos(pi) = -1.0
2 pi = 6.283185307179586
Out[1]:
array([0., 0., 0., 0., 0.])

A semicolon suppresses the automatic display of the final expression:

In [2]:
np.ones(5);

The kernel is stateful¶

Variables, imports, and function definitions remain in the kernel until they are deleted or the kernel is restarted. Execution counts such as In [4] record the order in which cells were run—not their position in the notebook.

In [3]:
radius = 3.0
area = np.pi*radius**2
area
Out[3]:
28.274333882308138
In [9]:
print(area)
del area
print(area)
28.274333882308138
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 3
      1 print(area)
      2 del area
----> 3 print(area)

NameError: name 'area' is not defined
In [8]:
# After the first execution, remove the next line and run this cell repeatedly.
counter = 0
counter += 1
counter
Out[8]:
1

After the preceding cell has been executed once, try deleting the line counter = 0 and running it repeatedly. This demonstrates why running cells out of order can make a notebook difficult to reproduce.

A reliable notebook should run correctly from top to bottom in a fresh kernel. Before sharing important work, use Restart Kernel and Run All Cells. If that fails, the notebook was relying on hidden state.

Completion, documentation, and inspection¶

Jupyter provides several ways to discover an unfamiliar library:

  • type part of a name and press Tab for completion;
  • place the cursor inside a function call and press Shift-Tab to inspect its signature;
  • append ? to a name for documentation;
  • append ?? to request additional details and source code when available;
  • use Python's help(...) and dir(...) functions.

Focused inspection is generally more useful than printing every name in a large module.

In [11]:
from scipy import integrate
integrate.quad?
Signature:
integrate.quad(
    func,
    a,
    b,
    args=(),
    full_output=0,
    epsabs=1.49e-08,
    epsrel=1.49e-08,
    limit=50,
    points=None,
    weight=None,
    wvar=None,
    wopts=None,
    maxp1=50,
    limlst=50,
    complex_func=False,
)
Docstring:
Compute a definite integral.

Integrate func from `a` to `b` (possibly infinite interval) using a
technique from the Fortran library QUADPACK.

Parameters
----------
func : {function, scipy.LowLevelCallable}
    A Python function or method to integrate. If `func` takes many
    arguments, it is integrated along the axis corresponding to the
    first argument.
    
    If the user desires improved integration performance, then `f` may
    be a `scipy.LowLevelCallable` with one of the signatures::
    
        double func(double x)
        double func(double x, void *user_data)
        double func(int n, double *xx)
        double func(int n, double *xx, void *user_data)
    
    The ``user_data`` is the data contained in the `scipy.LowLevelCallable`.
    In the call forms with ``xx``,  ``n`` is the length of the ``xx``
    array which contains ``xx[0] == x`` and the rest of the items are
    numbers contained in the ``args`` argument of quad.
    
    In addition, certain ctypes call signatures are supported for
    backward compatibility, but those should not be used in new code.
a : float
    Lower limit of integration (use -numpy.inf for -infinity).
b : float
    Upper limit of integration (use numpy.inf for +infinity).
args : tuple, optional
    Extra arguments to pass to `func`.
full_output : int, optional
    Non-zero to return a dictionary of integration information.
    If non-zero, warning messages are also suppressed and the
    message is appended to the output tuple.
complex_func : bool, optional
    Indicate if the function's (`func`) return type is real
    (``complex_func=False``: default) or complex (``complex_func=True``).
    In both cases, the function's argument is real.
    If full_output is also non-zero, the `infodict`, `message`, and
    `explain` for the real and complex components are returned in
    a dictionary with keys "real output" and "imag output".

Returns
-------
y : float
    The integral of func from `a` to `b`.
abserr : float
    An estimate of the absolute error in the result.
infodict : dict
    A dictionary containing additional information.
message
    A convergence message.
explain
    Appended only with 'cos' or 'sin' weighting and infinite
    integration limits, it contains an explanation of the codes in
    infodict['ierlst']

Other Parameters
----------------
epsabs : float or int, optional
    Absolute error tolerance. Default is 1.49e-8. `quad` tries to obtain
    an accuracy of ``abs(i-result) <= max(epsabs, epsrel*abs(i))``
    where ``i`` = integral of `func` from `a` to `b`, and ``result`` is the
    numerical approximation. See `epsrel` below.
epsrel : float or int, optional
    Relative error tolerance. Default is 1.49e-8.
    If ``epsabs <= 0``, `epsrel` must be greater than both 5e-29
    and ``50 * (machine epsilon)``. See `epsabs` above.
limit : float or int, optional
    An upper bound on the number of subintervals used in the adaptive
    algorithm.
points : (sequence of floats,ints), optional
    A sequence of break points in the bounded integration interval
    where local difficulties of the integrand may occur (e.g.,
    singularities, discontinuities). The sequence does not have
    to be sorted. Note that this option cannot be used in conjunction
    with ``weight``.
weight : float or int, optional
    String indicating weighting function. Full explanation for this
    and the remaining arguments can be found below.
wvar : optional
    Variables for use with weighting functions.
wopts : optional
    Optional input for reusing Chebyshev moments.
maxp1 : float or int, optional
    An upper bound on the number of Chebyshev moments.
limlst : int, optional
    Upper bound on the number of cycles (>=3) for use with a sinusoidal
    weighting and an infinite end-point.

See Also
--------

:func:`dblquad`
    double integral
:func:`tplquad`
    triple integral
:func:`nquad`
    n-dimensional integrals (uses `quad` recursively)
:func:`fixed_quad`
    fixed-order Gaussian quadrature
:func:`simpson`
    integrator for sampled data
:func:`romb`
    integrator for sampled data
:func:`scipy.special`
    for coefficients and roots of orthogonal polynomials


Notes
-----
For valid results, the integral must converge; behavior for divergent
integrals is not guaranteed.

**Extra information for quad() inputs and outputs**

If full_output is non-zero, then the third output argument
(infodict) is a dictionary with entries as tabulated below. For
infinite limits, the range is transformed to (0,1) and the
optional outputs are given with respect to this transformed range.
Let M be the input argument limit and let K be infodict['last'].
The entries are:

'neval'
    The number of function evaluations.
'last'
    The number, K, of subintervals produced in the subdivision process.
'alist'
    A rank-1 array of length M, the first K elements of which are the
    left end points of the subintervals in the partition of the
    integration range.
'blist'
    A rank-1 array of length M, the first K elements of which are the
    right end points of the subintervals.
'rlist'
    A rank-1 array of length M, the first K elements of which are the
    integral approximations on the subintervals.
'elist'
    A rank-1 array of length M, the first K elements of which are the
    moduli of the absolute error estimates on the subintervals.
'iord'
    A rank-1 integer array of length M, the first L elements of
    which are pointers to the error estimates over the subintervals
    with ``L=K`` if ``K<=M/2+2`` or ``L=M+1-K`` otherwise. Let I be the
    sequence ``infodict['iord']`` and let E be the sequence
    ``infodict['elist']``.  Then ``E[I[1]], ..., E[I[L]]`` forms a
    decreasing sequence.

If the input argument points is provided (i.e., it is not None),
the following additional outputs are placed in the output
dictionary. Assume the points sequence is of length P.

'pts'
    A rank-1 array of length P+2 containing the integration limits
    and the break points of the intervals in ascending order.
    This is an array giving the subintervals over which integration
    will occur.
'level'
    A rank-1 integer array of length M (=limit), containing the
    subdivision levels of the subintervals, i.e., if (aa,bb) is a
    subinterval of ``(pts[1], pts[2])`` where ``pts[0]`` and ``pts[2]``
    are adjacent elements of ``infodict['pts']``, then (aa,bb) has level l
    if ``|bb-aa| = |pts[2]-pts[1]| * 2**(-l)``.
'ndin'
    A rank-1 integer array of length P+2. After the first integration
    over the intervals (pts[1], pts[2]), the error estimates over some
    of the intervals may have been increased artificially in order to
    put their subdivision forward. This array has ones in slots
    corresponding to the subintervals for which this happens.

**Weighting the integrand**

The input variables, *weight* and *wvar*, are used to weight the
integrand by a select list of functions. Different integration
methods are used to compute the integral with these weighting
functions, and these do not support specifying break points. The
possible values of weight and the corresponding weighting functions are.

==========  ===================================   =====================
``weight``  Weight function used                  ``wvar``
==========  ===================================   =====================
'cos'       cos(w*x)                              wvar = w
'sin'       sin(w*x)                              wvar = w
'alg'       g(x) = ((x-a)**alpha)*((b-x)**beta)   wvar = (alpha, beta)
'alg-loga'  g(x)*log(x-a)                         wvar = (alpha, beta)
'alg-logb'  g(x)*log(b-x)                         wvar = (alpha, beta)
'alg-log'   g(x)*log(x-a)*log(b-x)                wvar = (alpha, beta)
'cauchy'    1/(x-c)                               wvar = c
==========  ===================================   =====================

wvar holds the parameter w, (alpha, beta), or c depending on the weight
selected. In these expressions, a and b are the integration limits.

For the 'cos' and 'sin' weighting, additional inputs and outputs are
available.

For weighted integrals with finite integration limits, the integration
is performed using a Clenshaw-Curtis method, which uses Chebyshev moments.
For repeated calculations, these moments are saved in the output dictionary:

'momcom'
    The maximum level of Chebyshev moments that have been computed,
    i.e., if ``M_c`` is ``infodict['momcom']`` then the moments have been
    computed for intervals of length ``|b-a| * 2**(-l)``,
    ``l=0,1,...,M_c``.
'nnlog'
    A rank-1 integer array of length M(=limit), containing the
    subdivision levels of the subintervals, i.e., an element of this
    array is equal to l if the corresponding subinterval is
    ``|b-a|* 2**(-l)``.
'chebmo'
    A rank-2 array of shape (25, maxp1) containing the computed
    Chebyshev moments. These can be passed on to an integration
    over the same interval by passing this array as the second
    element of the sequence wopts and passing infodict['momcom'] as
    the first element.

If one of the integration limits is infinite, then a Fourier integral is
computed (assuming w neq 0). If full_output is 1 and a numerical error
is encountered, besides the error message attached to the output tuple,
a dictionary is also appended to the output tuple which translates the
error codes in the array ``info['ierlst']`` to English messages. The
output information dictionary contains the following entries instead of
'last', 'alist', 'blist', 'rlist', and 'elist':

'lst'
    The number of subintervals needed for the integration (call it ``K_f``).
'rslst'
    A rank-1 array of length M_f=limlst, whose first ``K_f`` elements
    contain the integral contribution over the interval
    ``(a+(k-1)c, a+kc)`` where ``c = (2*floor(|w|) + 1) * pi / |w|``
    and ``k=1,2,...,K_f``.
'erlst'
    A rank-1 array of length ``M_f`` containing the error estimate
    corresponding to the interval in the same position in
    ``infodict['rslist']``.
'ierlst'
    A rank-1 integer array of length ``M_f`` containing an error flag
    corresponding to the interval in the same position in
    ``infodict['rslist']``.  See the explanation dictionary (last entry
    in the output tuple) for the meaning of the codes.

**Details of QUADPACK level routines**

`quad` calls routines from the FORTRAN library QUADPACK. This section
provides details on the conditions for each routine to be called and a
short description of each routine. The routine called depends on
`weight`, `points` and the integration limits `a` and `b`.

================  ==============  ==========  =====================
QUADPACK routine  `weight`        `points`    infinite bounds
================  ==============  ==========  =====================
qagse             None            No          No
qagie             None            No          Yes
qagpe             None            Yes         No
qawoe             'sin', 'cos'    No          No
qawfe             'sin', 'cos'    No          either `a` or `b`
qawse             'alg*'          No          No
qawce             'cauchy'        No          No
================  ==============  ==========  =====================

The following provides a short description from [1]_ for each
routine.

qagse
    is an integrator based on globally adaptive interval
    subdivision in connection with extrapolation, which will
    eliminate the effects of integrand singularities of
    several types. The integration is performed using a 21-point Gauss-Kronrod 
    quadrature within each subinterval.
qagie
    handles integration over infinite intervals. The infinite range is
    mapped onto a finite interval and subsequently the same strategy as
    in ``QAGS`` is applied.
qagpe
    serves the same purposes as QAGS, but also allows the
    user to provide explicit information about the location
    and type of trouble-spots i.e. the abscissae of internal
    singularities, discontinuities and other difficulties of
    the integrand function.
qawoe
    is an integrator for the evaluation of
    :math:`\int^b_a \cos(\omega x)f(x)dx` or
    :math:`\int^b_a \sin(\omega x)f(x)dx`
    over a finite interval [a,b], where :math:`\omega` and :math:`f`
    are specified by the user. The rule evaluation component is based
    on the modified Clenshaw-Curtis technique

    An adaptive subdivision scheme is used in connection
    with an extrapolation procedure, which is a modification
    of that in ``QAGS`` and allows the algorithm to deal with
    singularities in :math:`f(x)`.
qawfe
    calculates the Fourier transform
    :math:`\int^\infty_a \cos(\omega x)f(x)dx` or
    :math:`\int^\infty_a \sin(\omega x)f(x)dx`
    for user-provided :math:`\omega` and :math:`f`. The procedure of
    ``QAWO`` is applied on successive finite intervals, and convergence
    acceleration by means of the :math:`\varepsilon`-algorithm is applied
    to the series of integral approximations.
qawse
    approximate :math:`\int^b_a w(x)f(x)dx`, with :math:`a < b` where
    :math:`w(x) = (x-a)^{\alpha}(b-x)^{\beta}v(x)` with
    :math:`\alpha,\beta > -1`, where :math:`v(x)` may be one of the
    following functions: :math:`1`, :math:`\log(x-a)`, :math:`\log(b-x)`,
    :math:`\log(x-a)\log(b-x)`.

    The user specifies :math:`\alpha`, :math:`\beta` and the type of the
    function :math:`v`. A globally adaptive subdivision strategy is
    applied, with modified Clenshaw-Curtis integration on those
    subintervals which contain `a` or `b`.
qawce
    compute :math:`\int^b_a f(x) / (x-c)dx` where the integral must be
    interpreted as a Cauchy principal value integral, for user specified
    :math:`c` and :math:`f`. The strategy is globally adaptive. Modified
    Clenshaw-Curtis integration is used on those intervals containing the
    point :math:`x = c`.

**Integration of Complex Function of a Real Variable**

A complex valued function, :math:`f`, of a real variable can be written as
:math:`f = g + ih`.  Similarly, the integral of :math:`f` can be
written as

.. math::
    \int_a^b f(x) dx = \int_a^b g(x) dx + i\int_a^b h(x) dx

assuming that the integrals of :math:`g` and :math:`h` exist
over the interval :math:`[a,b]` [2]_. Therefore, ``quad`` integrates
complex-valued functions by integrating the real and imaginary components
separately.

**Array API Standard Support**

`quad` has experimental support for Python Array API Standard compatible
backends in addition to NumPy. Please consider testing these features
by setting an environment variable ``SCIPY_ARRAY_API=1`` and providing
CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following
combinations of backend and device (or other capability) are supported.

====================  ====================  ====================
Library               CPU                   GPU
====================  ====================  ====================
NumPy                 ✅                     n/a                 
CuPy                  n/a                   ⛔                   
PyTorch               ⛔                     ⛔                   
JAX                   ⛔                     ⛔                   
Dask                  ⛔                     n/a                 
====================  ====================  ====================

    See :ref:`dev-arrayapi` for more information.

References
----------
.. [1] Piessens, Robert; de Doncker-Kapenga, Elise;
       Überhuber, Christoph W.; Kahaner, David (1983).
       QUADPACK: A subroutine package for automatic integration.
       Springer-Verlag.
       ISBN 978-3-540-12553-2.

.. [2] McCullough, Thomas; Phillips, Keith (1973).
       Foundations of Analysis in the Complex Plane.
       Holt Rinehart Winston.
       ISBN 0-03-086370-8

Examples
--------
Calculate :math:`\int^4_0 x^2 dx` and compare with an analytic result

>>> from scipy import integrate
>>> import numpy as np
>>> x2 = lambda x: x**2
>>> integrate.quad(x2, 0, 4)
(21.333333333333332, 2.3684757858670003e-13)
>>> print(4**3 / 3.)  # analytical result
21.3333333333

Calculate :math:`\int^\infty_0 e^{-x} dx`

>>> invexp = lambda x: np.exp(-x)
>>> integrate.quad(invexp, 0, np.inf)
(1.0, 5.842605999138044e-11)

Calculate :math:`\int^1_0 a x \,dx` for :math:`a = 1, 3`

>>> f = lambda x, a: a*x
>>> y, err = integrate.quad(f, 0, 1, args=(1,))
>>> y
0.5
>>> y, err = integrate.quad(f, 0, 1, args=(3,))
>>> y
1.5

Calculate :math:`\int^1_0 x^2 + y^2 dx` with ctypes, holding
y parameter as 1::

    testlib.c =>
        double func(int n, double args[n]){
            return args[0]*args[0] + args[1]*args[1];}
    compile to library testlib.*

::

   from scipy import integrate
   import ctypes
   lib = ctypes.CDLL('/home/.../testlib.*') #use absolute path
   lib.func.restype = ctypes.c_double
   lib.func.argtypes = (ctypes.c_int,ctypes.c_double)
   integrate.quad(lib.func,0,1,(1))
   #(1.3333333333333333, 1.4802973661668752e-14)
   print((1.0**3/3.0 + 1.0) - (0.0**3/3.0 + 0.0)) #Analytic result
   # 1.3333333333333333

Be aware that pulse shapes and other sharp features as compared to the
size of the integration interval may not be integrated correctly using
this method. A simplified example of this limitation is integrating a
y-axis reflected step function with many zero values within the integrals
bounds.

>>> y = lambda x: 1 if x<=0 else 0
>>> integrate.quad(y, -1, 1)
(1.0, 1.1102230246251565e-14)
>>> integrate.quad(y, -1, 100)
(1.0000000002199108, 1.0189464580163188e-08)
>>> integrate.quad(y, -1, 10000)
(0.0, 0.0)
File:      ~/miniconda3-clean/envs/work311/lib/python3.11/site-packages/scipy/integrate/_quadpack_py.py
Type:      function
In [12]:
# Find selected NumPy names rather than printing the entire module directory.
[name for name in dir(np) if name.startswith("log")]
Out[12]:
['log',
 'log10',
 'log1p',
 'log2',
 'logaddexp',
 'logaddexp2',
 'logical_and',
 'logical_not',
 'logical_or',
 'logical_xor',
 'logspace']

For example, scipy.integrate.quad returns both the numerical integral and an estimate of its absolute error:

In [13]:
value, estimated_error = integrate.quad(lambda x: np.exp(-x), 0, np.inf)
print("integral =", value)
print("estimated absolute error =", estimated_error)
integral = 1.0
estimated absolute error = 5.842605965544164e-11

Figures appear inline¶

Matplotlib figures produced by a code cell are displayed as part of the notebook output.

In [14]:
import matplotlib.pyplot as plt

x = np.linspace(0, 2*np.pi, 300)
plt.plot(x, np.sin(x), label=r"$\sin x$")
plt.plot(x, np.cos(x), label=r"$\cos x$")
plt.xlabel(r"$x$")
plt.ylabel("function value")
plt.legend()
plt.show()
No description has been provided for this image

IPython commands: magics and the system shell¶

A Python kernel in Jupyter normally runs through IPython. IPython adds convenient syntax that is not part of the Python language:

  • line magics begin with %, for example %timeit and %pwd;
  • cell magics begin with %% and apply to an entire cell;
  • !command sends a command to the system shell.

Shell commands are operating-system dependent and can make a notebook less portable.

In [15]:
%pwd
Out[15]:
'/Users/haule/Teaching/ComputationalPhysics/2026/Programing/src'
In [16]:
%timeit np.sin(x)
906 ns ± 4.95 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [17]:
# These are shell commands, not Python statements.
!pwd
!ls
/Users/haule/Teaching/ComputationalPhysics/2026/Programing/src
__pycache__
00_Introduction_before_revision_2026-09-18.ipynb
00_Introduction.ipynb
01_Basic_Python_with_solution.ipynb
01_Basic_Python_with_solution2.ipynb
01_Basic_Python.html
01_Basic_Python.ipynb
02_Numpy_with_solution.ipynb
02_Numpy.html
02_Numpy.ipynb
03_Scipy_old.ipynb
03_Scipy.html
03_Scipy.ipynb
04_Scipy_Hydrogen_atom_with_solution.ipynb
04_Scipy_Hydrogen_atom.ipynb
05_Atom_in_LDA_.html
05_Atom_in_LDA_.ipynb
05_Atom_in_LDA.html
05_Atom_in_LDA.ipynb
06_ODE_solution.ipynb
06_ODE.html
06_ODE.ipynb
anaconda_projects
backup
double_pendulum.mp4
excor.py
img
my_out.txt
mymodule.py
old
old2
pendulum.py
ST_data.npy
stockholm_daily_mean_temperature.csv
stockholm_td_adj.dat
stockholm_td_adj.dat.txt
StockholmT.dat
tmp

Which Python environment is the notebook using?¶

The notebook kernel—not the terminal from which Jupyter was launched—determines which Python interpreter and packages execute the code. The kernel name is displayed near the upper-right corner of the notebook interface.

sys.executable gives the interpreter used by the active Python kernel:

In [19]:
import sys

print(sys.executable)
print("Python", sys.version.split()[0])
print("NumPy", np.__version__)
!which python
/Users/haule/miniconda3-clean/envs/work311/bin/python
Python 3.11.14
NumPy 2.3.5
/Users/haule/miniconda3-clean/envs/work311/bin/python

By contrast, !which python only reports which executable the shell finds through its PATH; it can differ from the active kernel.

When a package must be installed from a notebook, prefer IPython's %pip magic:

%pip install package_name

It targets the current kernel environment more reliably than !pip install .... A kernel restart may be necessary after installing or updating a package. For this course, install the required packages in advance rather than modifying the environment during every notebook run.

Files and working directories¶

Relative paths are interpreted from the kernel's current working directory, which may not be the directory you expected. Inspect it explicitly when reading data files:

In [20]:
from pathlib import Path

working_directory = Path.cwd()
print(working_directory)
print("Notebook files here:", [p.name for p in working_directory.glob("*.ipynb")])
/Users/haule/Teaching/ComputationalPhysics/2026/Programing/src
Notebook files here: ['00_Introduction.ipynb', '03_Scipy_old.ipynb', '01_Basic_Python_with_solution.ipynb', '04_Scipy_Hydrogen_atom.ipynb', '00_Introduction_before_revision_2026-09-18.ipynb', '01_Basic_Python.ipynb', '06_ODE.ipynb', '02_Numpy_with_solution.ipynb', '06_ODE_solution.ipynb', '02_Numpy.ipynb', '05_Atom_in_LDA.ipynb', '05_Atom_in_LDA_.ipynb', '03_Scipy.ipynb', '04_Scipy_Hydrogen_atom_with_solution.ipynb', '01_Basic_Python_with_solution2.ipynb']

Prefer pathlib.Path over manually concatenating directory strings:

data_file = Path("data") / "measurement.csv"

Do not hard-code a personal absolute path when the notebook will be shared with other people.

Saving, outputs, and reproducibility¶

An .ipynb file is a JSON document containing cell sources, metadata, and any outputs that were saved. Saved output is only a snapshot; it does not prove that the current code produced it.

Before sharing a notebook:

  1. Save a backup if it contains important work.
  2. Restart the kernel and run all cells from top to bottom.
  3. Check that there are no errors or hidden dependencies on execution order.
  4. Remove large, obsolete, private, or machine-specific outputs.
  5. Use relative paths and state the packages or environment required.

If a file changes on disk while it is open in the browser, reload it before saving. Otherwise, the older browser copy may overwrite the newer file.

Sharing notebooks¶

A saved .ipynb file can be sent directly to another person. Common alternatives are:

  • export a static HTML copy with jupyter nbconvert --to html notebook.ipynb;
  • render a public notebook statically on GitHub or nbviewer;
  • provide an executable environment through Binder;
  • use JupyterHub to provide managed notebook environments to a class.

Static HTML, GitHub, and nbviewer displays do not execute the Python cells. Reproducing the calculation requires both the notebook and a compatible Python environment.

Notebook security¶

A notebook can contain arbitrary Python and shell commands. Read unfamiliar notebooks before executing them, especially cells that access files, install software, use credentials, or communicate over the network.

Jupyter distinguishes trusted and untrusted notebook output. Trusting a notebook affects whether previously saved HTML and JavaScript output is displayed; it does not make arbitrary code safe to run.

Finishing a session¶

Saving the notebook and stopping the kernel are different actions:

  • Save writes the notebook document and its current outputs to disk.
  • Restart Kernel clears all Python variables and imports but keeps the notebook open.
  • Interrupt Kernel stops a calculation that is currently running.
  • Shut Down Kernel/Notebook terminates the Python process.
  • Closing the browser tab alone may leave the kernel running.

For a final check, save the notebook, restart the kernel, run all cells, and confirm that the results can be reproduced in order.