diff --git a/notebooks/tests/animate.ipynb b/notebooks/tests/animate.ipynb
index 5fe919980560ebb51b8be5357075a7104290ff41..ca4c457eb86a5f88411a45bd0ceb66dc53d72e18 100644
--- a/notebooks/tests/animate.ipynb
+++ b/notebooks/tests/animate.ipynb
@@ -14,7 +14,7 @@
        "\n",
        "Show information about the current state.\n",
        "\n",
-       "The output shows the names of all sets, constants, and variables defined by the current machine, as well as a list of transitions that are available in the current state. Each transition has a numeric ID, which can be passed to `:exec` to execute that transition."
+       "The output shows the names of all sets, constants, and variables defined by the current machine, as well as a list of transitions that are available in the current state."
       ],
       "text/plain": [
        "```\n",
@@ -23,7 +23,7 @@
        "\n",
        "Show information about the current state.\n",
        "\n",
-       "The output shows the names of all sets, constants, and variables defined by the current machine, as well as a list of transitions that are available in the current state. Each transition has a numeric ID, which can be passed to `:exec` to execute that transition."
+       "The output shows the names of all sets, constants, and variables defined by the current machine, as well as a list of transitions that are available in the current state."
       ]
      },
      "execution_count": 1,
@@ -45,26 +45,20 @@
       "text/markdown": [
        "```\n",
        ":exec OPERATION [PREDICATE]\n",
-       ":exec OPERATION_ID\n",
        "```\n",
        "\n",
-       "Execute an operation with the specified predicate, or by its ID.\n",
+       "Execute an operation.\n",
        "\n",
-       "In the first form, the given operation is executed. If the optional predicate is specified, a transition is found for which the predicate is $\\mathit{TRUE}$. The predicate can be used to restrict what values the operation's parameters or the variables in the next state may have.\n",
-       "\n",
-       "In the second form, a known transition with the given numeric ID is executed. A list of the current state's available transitions and their IDs can be viewed using `:browse`. Only transition IDs from the current state can be executed."
+       "A transition for the given operation is found and executed. If the optional predicate is specified, a transition is found for which the predicate is $\\mathit{TRUE}$. The predicate can be used to restrict what values the operation's parameters or the variables in the next state may have."
       ],
       "text/plain": [
        "```\n",
        ":exec OPERATION [PREDICATE]\n",
-       ":exec OPERATION_ID\n",
        "```\n",
        "\n",
-       "Execute an operation with the specified predicate, or by its ID.\n",
-       "\n",
-       "In the first form, the given operation is executed. If the optional predicate is specified, a transition is found for which the predicate is $\\mathit{TRUE}$. The predicate can be used to restrict what values the operation's parameters or the variables in the next state may have.\n",
+       "Execute an operation.\n",
        "\n",
-       "In the second form, a known transition with the given numeric ID is executed. A list of the current state's available transitions and their IDs can be viewed using `:browse`. Only transition IDs from the current state can be executed."
+       "A transition for the given operation is found and executed. If the optional predicate is specified, a transition is found for which the predicate is $\\mathit{TRUE}$. The predicate can be used to restrict what values the operation's parameters or the variables in the next state may have."
       ]
      },
      "execution_count": 2,
@@ -159,7 +153,7 @@
        "Constants: (none)\n",
        "Variables: (none)\n",
        "Operations: \n",
-       "0: $initialise_machine()"
+       "INITIALISATION()"
       ]
      },
      "execution_count": 5,
@@ -217,10 +211,10 @@
        "Constants: min_value, max_value\n",
        "Variables: value\n",
        "Operations: \n",
-       "0: $setup_constants()\n",
-       "1: $setup_constants()\n",
-       "2: $setup_constants()\n",
-       "3: $setup_constants()\n",
+       "SETUP_CONSTANTS()\n",
+       "SETUP_CONSTANTS()\n",
+       "SETUP_CONSTANTS()\n",
+       "SETUP_CONSTANTS()\n",
        "More operations may be available (MAX_OPERATIONS/MAX_INITIALISATIONS reached)"
       ]
      },
@@ -329,10 +323,10 @@
        "Constants: min_value, max_value\n",
        "Variables: value\n",
        "Operations: \n",
-       "4: $initialise_machine()\n",
-       "5: $initialise_machine()\n",
-       "6: $initialise_machine()\n",
-       "7: $initialise_machine()\n",
+       "INITIALISATION()\n",
+       "INITIALISATION()\n",
+       "INITIALISATION()\n",
+       "INITIALISATION()\n",
        "More operations may be available (MAX_OPERATIONS/MAX_INITIALISATIONS reached)"
       ]
      },
@@ -439,10 +433,10 @@
        "Constants: min_value, max_value\n",
        "Variables: value\n",
        "Operations: \n",
-       "8: add(-3)\n",
-       "9: add(-2)\n",
-       "10: add(-1)\n",
-       "11: add(0)"
+       "add(-3)\n",
+       "add(-2)\n",
+       "add(-1)\n",
+       "add(0)"
       ]
      },
      "execution_count": 17,
@@ -485,7 +479,7 @@
     {
      "data": {
       "text/plain": [
-       "Executed operation 10: add(-1)"
+       "Executed operation: add(-1)"
       ]
      },
      "execution_count": 19,
@@ -510,10 +504,10 @@
        "Constants: min_value, max_value\n",
        "Variables: value\n",
        "Operations: \n",
-       "12: add(-2)\n",
-       "13: add(-1)\n",
-       "14: add(0)\n",
-       "15: add(1)"
+       "add(-2)\n",
+       "add(-1)\n",
+       "add(0)\n",
+       "add(1)"
       ]
      },
      "execution_count": 20,
@@ -552,72 +546,6 @@
    "cell_type": "code",
    "execution_count": 22,
    "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "Executed operation 15: add(1)"
-      ]
-     },
-     "execution_count": 22,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    ":exec 15"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 23,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "Machine: Counter\n",
-       "Sets: (none)\n",
-       "Constants: min_value, max_value\n",
-       "Variables: value\n",
-       "Operations: \n",
-       "8: add(-3)\n",
-       "9: add(-2)\n",
-       "10: add(-1)\n",
-       "11: add(0)"
-      ]
-     },
-     "execution_count": 23,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    ":browse"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 24,
-   "metadata": {},
-   "outputs": [
-    {
-     "ename": "CommandExecutionException",
-     "evalue": ":exec: Cannot specify a predicate when executing an operation by ID",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[1m\u001b[31m:exec: Cannot specify a predicate when executing an operation by ID\u001b[0m"
-     ]
-    }
-   ],
-   "source": [
-    ":exec 10 1=1"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 25,
-   "metadata": {},
    "outputs": [
     {
      "data": {
@@ -630,7 +558,7 @@
        "Transitions: 16"
       ]
      },
-     "execution_count": 25,
+     "execution_count": 22,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -648,7 +576,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 23,
    "metadata": {},
    "outputs": [
     {
@@ -675,7 +603,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 24,
    "metadata": {},
    "outputs": [
     {
@@ -702,7 +630,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
+   "execution_count": 25,
    "metadata": {},
    "outputs": [
     {
@@ -713,7 +641,7 @@
       "\u001b[1m\u001b[31m---------------------------------------------------------------------------\u001b[0m",
       "\u001b[1m\u001b[31mjava.lang.IllegalArgumentException: Executing operation nope with predicate 1=1 produced errors: Unknown Operation nope\u001b[0m",
       "\u001b[1m\u001b[31m\tat de.prob.statespace.StateSpace.transitionFromPredicate(StateSpace.java:279)\u001b[0m",
-      "\u001b[1m\u001b[31m\tat de.prob2.jupyter.commands.ExecCommand.run(ExecCommand.java:70)\u001b[0m",
+      "\u001b[1m\u001b[31m\tat de.prob2.jupyter.commands.ExecCommand.run(ExecCommand.java:53)\u001b[0m",
       "\u001b[1m\u001b[31m\tat de.prob2.jupyter.ProBKernel.executeCommand(ProBKernel.java:226)\u001b[0m",
       "\u001b[1m\u001b[31m\tat de.prob2.jupyter.ProBKernel.eval(ProBKernel.java:249)\u001b[0m",
       "\u001b[1m\u001b[31m\tat io.github.spencerpark.jupyter.kernel.BaseKernel.handleExecuteRequest(BaseKernel.java:303)\u001b[0m",
@@ -729,7 +657,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
+   "execution_count": 26,
    "metadata": {},
    "outputs": [
     {
@@ -740,7 +668,7 @@
       "\u001b[1m\u001b[31m---------------------------------------------------------------------------\u001b[0m",
       "\u001b[1m\u001b[31mjava.lang.IllegalArgumentException: Executing operation add with predicate 1=0 produced errors: Could not execute operation add with additional predicate\u001b[0m",
       "\u001b[1m\u001b[31m\tat de.prob.statespace.StateSpace.transitionFromPredicate(StateSpace.java:279)\u001b[0m",
-      "\u001b[1m\u001b[31m\tat de.prob2.jupyter.commands.ExecCommand.run(ExecCommand.java:70)\u001b[0m",
+      "\u001b[1m\u001b[31m\tat de.prob2.jupyter.commands.ExecCommand.run(ExecCommand.java:53)\u001b[0m",
       "\u001b[1m\u001b[31m\tat de.prob2.jupyter.ProBKernel.executeCommand(ProBKernel.java:226)\u001b[0m",
       "\u001b[1m\u001b[31m\tat de.prob2.jupyter.ProBKernel.eval(ProBKernel.java:249)\u001b[0m",
       "\u001b[1m\u001b[31m\tat io.github.spencerpark.jupyter.kernel.BaseKernel.handleExecuteRequest(BaseKernel.java:303)\u001b[0m",
@@ -756,7 +684,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
+   "execution_count": 27,
    "metadata": {},
    "outputs": [
     {
@@ -765,7 +693,7 @@
        "Loaded machine: NoConstants"
       ]
      },
-     "execution_count": 30,
+     "execution_count": 27,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -781,7 +709,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 28,
    "metadata": {},
    "outputs": [
     {
@@ -808,7 +736,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 32,
+   "execution_count": 29,
    "metadata": {},
    "outputs": [
     {
@@ -835,7 +763,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": 30,
    "metadata": {},
    "outputs": [
     {
@@ -844,7 +772,7 @@
        "Loaded machine: Foo"
       ]
      },
-     "execution_count": 33,
+     "execution_count": 30,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -862,16 +790,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
+   "execution_count": 31,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "Executed operation 2: $setup_constants()"
+       "Executed operation: $setup_constants()"
       ]
      },
-     "execution_count": 34,
+     "execution_count": 31,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -882,16 +810,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
+   "execution_count": 32,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "Executed operation 7: $initialise_machine()"
+       "Executed operation: $initialise_machine()"
       ]
      },
-     "execution_count": 35,
+     "execution_count": 32,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -902,7 +830,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
+   "execution_count": 33,
    "metadata": {},
    "outputs": [
     {
@@ -914,7 +842,7 @@
        "1"
       ]
      },
-     "execution_count": 36,
+     "execution_count": 33,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -925,7 +853,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": 34,
    "metadata": {},
    "outputs": [
     {
@@ -937,7 +865,7 @@
        "2"
       ]
      },
-     "execution_count": 37,
+     "execution_count": 34,
      "metadata": {},
      "output_type": "execute_result"
     }
diff --git a/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java b/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java
index c95f78d9c3dc17e9c8ba4e593afd234ce411fd28..d6b8d353cf4b894572bc360f75fb5ee08a8a39b2 100644
--- a/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java
@@ -44,7 +44,7 @@ public final class BrowseCommand implements Command {
 	
 	@Override
 	public @NotNull String getHelpBody() {
-		return "The output shows the names of all sets, constants, and variables defined by the current machine, as well as a list of transitions that are available in the current state. Each transition has a numeric ID, which can be passed to `:exec` to execute that transition.";
+		return "The output shows the names of all sets, constants, and variables defined by the current machine, as well as a list of transitions that are available in the current state.";
 	}
 	
 	private static @NotNull String listToString(final List<String> list) {
@@ -69,13 +69,12 @@ public final class BrowseCommand implements Command {
 		sb.append(listToString(lm.getVariableNames()));
 		sb.append("\nOperations: ");
 		final List<Transition> sortedTransitions = new ArrayList<>(trace.getNextTransitions(true, FormulaExpand.TRUNCATE));
+		// Sort transitions by ID to get a consistent ordering.
 		// Transition IDs are strings, but they almost always contain numbers.
 		sortedTransitions.sort(Comparator.comparing(Transition::getId, new AlphanumericComparator()));
 		for (final Transition t : sortedTransitions) {
 			sb.append('\n');
-			sb.append(t.getId());
-			sb.append(": ");
-			sb.append(t.getRep());
+			sb.append(t.getPrettyRep());
 		}
 		if (trace.getCurrentState().isMaxTransitionsCalculated()) {
 			sb.append("\nMore operations may be available (MAX_OPERATIONS/MAX_INITIALISATIONS reached)");
diff --git a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
index 75453e702fac5ba0b3f1be75570646a504576fc3..8d679fc0386dbb89e838ef3597e2b561409bbf39 100644
--- a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
@@ -2,10 +2,8 @@ package de.prob2.jupyter.commands;
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import com.google.inject.Inject;
 
@@ -14,8 +12,6 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob.statespace.Transition;
 
-import de.prob2.jupyter.UserErrorException;
-
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
@@ -33,48 +29,34 @@ public final class ExecCommand implements Command {
 	
 	@Override
 	public @NotNull String getSyntax() {
-		return ":exec OPERATION [PREDICATE]\n:exec OPERATION_ID";
+		return ":exec OPERATION [PREDICATE]";
 	}
 	
 	@Override
 	public @NotNull String getShortHelp() {
-		return "Execute an operation with the specified predicate, or by its ID.";
+		return "Execute an operation.";
 	}
 	
 	@Override
 	public @NotNull String getHelpBody() {
-		return "In the first form, the given operation is executed. If the optional predicate is specified, a transition is found for which the predicate is $\\mathit{TRUE}$. The predicate can be used to restrict what values the operation's parameters or the variables in the next state may have.\n\n"
-			+ "In the second form, a known transition with the given numeric ID is executed. A list of the current state's available transitions and their IDs can be viewed using `:browse`. Only transition IDs from the current state can be executed.";
+		return "A transition for the given operation is found and executed. If the optional predicate is specified, a transition is found for which the predicate is $\\mathit{TRUE}$. The predicate can be used to restrict what values the operation's parameters or the variables in the next state may have.";
 	}
 	
 	@Override
 	public @NotNull DisplayData run(final @NotNull String argString) {
 		final List<String> split = CommandUtils.splitArgs(argString, 2);
 		assert !split.isEmpty();
-		final String opNameOrId = split.get(0);
 		
 		final Trace trace = this.animationSelector.getCurrentTrace();
-		// Check if the argument is an ID, by searching for a transition with that ID.
-		final Optional<Transition> opt = trace.getNextTransitions().stream().filter(t -> t.getId().equals(opNameOrId)).findAny();
-		final Transition op;
-		if (opt.isPresent()) {
-			// Transition found, nothing else needs to be done.
-			if (split.size() != 1) {
-				throw new UserErrorException("Cannot specify a predicate when executing an operation by ID");
-			}
-			op = opt.get();
-		} else {
-			// Transition not found, assume that the argument is an operation name instead.
-			final String translatedOpName = CommandUtils.unprettyOperationName(opNameOrId);
-			final String predicate = split.size() < 2 ? "1=1" : split.get(1);
-			final List<Transition> ops = trace.getStateSpace().transitionFromPredicate(trace.getCurrentState(), translatedOpName, predicate, 1);
-			assert !ops.isEmpty();
-			op = ops.get(0);
-		}
+		final String translatedOpName = CommandUtils.unprettyOperationName(split.get(0));
+		final String predicate = split.size() < 2 ? "1=1" : split.get(1);
+		final List<Transition> ops = trace.getStateSpace().transitionFromPredicate(trace.getCurrentState(), translatedOpName, predicate, 1);
+		assert !ops.isEmpty();
+		final Transition op = ops.get(0);
 		
 		this.animationSelector.changeCurrentAnimation(trace.add(op));
 		trace.getStateSpace().evaluateTransitions(Collections.singleton(op), FormulaExpand.TRUNCATE);
-		return new DisplayData(String.format("Executed operation %s: %s", op.getId(), op.getRep()));
+		return new DisplayData(String.format("Executed operation: %s", op.getRep()));
 	}
 	
 	@Override
@@ -92,12 +74,13 @@ public final class ExecCommand implements Command {
 			final ReplacementOptions replacements = CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString.substring(opNameEnd), at - opNameEnd);
 			return CommandUtils.offsetReplacementOptions(replacements, opNameEnd);
 		} else {
-			// Cursor is in the first part of the arguments, provide possible operation names and transition IDs.
+			// Cursor is in the first part of the arguments, provide possible operation names.
 			final String prefix = argString.substring(0, at);
 			final List<String> opNames = this.animationSelector.getCurrentTrace()
 				.getNextTransitions()
 				.stream()
-				.flatMap(t -> Stream.of(CommandUtils.prettyOperationName(t.getName()), t.getId()))
+				.map(Transition::getName)
+				.map(CommandUtils::prettyOperationName)
 				.distinct()
 				.filter(s -> s.startsWith(prefix))
 				.sorted()