{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "# Converters"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "Converters are used to transform prompts before sending them to the target.\n",
    "\n",
    "This can be useful for a variety of reasons, such as encoding the prompt in a different format, or adding additional information to the prompt. For example, you might want to convert a prompt to base64 before sending it to the target, or add a prefix to the prompt to indicate that it is a question.\n",
    "\n",
    "Converters can transform prompts in various ways:\n",
    "- **Text-to-Text**: Encoding, obfuscation, translation, and semantic transformations\n",
    "- **Multimodal**: Converting between text, images, audio, video, and files\n",
    "\n",
    "## Converter Modality Reference Table\n",
    "\n",
    "The following table shows all available converters organized by their input and output modalities:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "No default environment files found. Using system environment variables only.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[pyrit:alembic] No new upgrade operations detected.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     Input Modality Output Modality                            Converter\n",
      "0        audio_path      audio_path                   AudioEchoConverter\n",
      "1        audio_path      audio_path              AudioFrequencyConverter\n",
      "2        audio_path      audio_path                  AudioSpeedConverter\n",
      "3        audio_path      audio_path                 AudioVolumeConverter\n",
      "4        audio_path      audio_path             AudioWhiteNoiseConverter\n",
      "5        audio_path            text      AzureSpeechAudioToTextConverter\n",
      "6        image_path      image_path                AddTextImageConverter\n",
      "7        image_path      image_path                ImageOverlayConverter\n",
      "8        image_path      image_path          TransparencyAttackConverter\n",
      "9        image_path      video_path               AddImageVideoConverter\n",
      "10  image_path, url      image_path        ImageColorSaturationConverter\n",
      "11  image_path, url      image_path            ImageCompressionConverter\n",
      "12  image_path, url      image_path               ImageResizingConverter\n",
      "13  image_path, url      image_path               ImageRotationConverter\n",
      "14             text      audio_path      AzureSpeechTextToAudioConverter\n",
      "15             text     binary_path                         PDFConverter\n",
      "16             text     binary_path                     WordDocConverter\n",
      "17             text      image_path                AddImageTextConverter\n",
      "18             text      image_path                      QRCodeConverter\n",
      "19             text            text                    AcrosticConverter\n",
      "20             text            text                  AnsiAttackConverter\n",
      "21             text            text      ArabicPresentationFormConverter\n",
      "22             text            text                     ArabiziConverter\n",
      "23             text            text                    AsciiArtConverter\n",
      "24             text            text               AsciiSmugglerConverter\n",
      "25             text            text                 AskToDecodeConverter\n",
      "26             text            text                      AtbashConverter\n",
      "27             text            text                    Base2048Converter\n",
      "28             text            text                      Base64Converter\n",
      "29             text            text                        BidiConverter\n",
      "30             text            text                    BinAsciiConverter\n",
      "31             text            text                      BinaryConverter\n",
      "32             text            text                     BrailleConverter\n",
      "33             text            text                      CaesarConverter\n",
      "34             text            text                   CharNoiseConverter\n",
      "35             text            text                    CharSwapConverter\n",
      "36             text            text              CharacterSpaceConverter\n",
      "37             text            text                  CodeAttackConverter\n",
      "38             text            text               CodeChameleonConverter\n",
      "39             text            text          ColloquialWordswapConverter\n",
      "40             text            text               DecompositionConverter\n",
      "41             text            text                    DenylistConverter\n",
      "42             text            text                   DiacriticConverter\n",
      "43             text            text                       EcojiConverter\n",
      "44             text            text                       EmojiConverter\n",
      "45             text            text                 FirstLetterConverter\n",
      "46             text            text                        FlipConverter\n",
      "47             text            text                         IPAConverter\n",
      "48             text            text            ImagePromptStyleConverter\n",
      "49             text            text           InsertPunctuationConverter\n",
      "50             text            text                  JsonStringConverter\n",
      "51             text            text              LLMGenericTextConverter\n",
      "52             text            text                   LeetspeakConverter\n",
      "53             text            text  MaliciousQuestionGeneratorConverter\n",
      "54             text            text             MathObfuscationConverter\n",
      "55             text            text                  MathPromptConverter\n",
      "56             text            text                       MorseConverter\n",
      "57             text            text                        NatoConverter\n",
      "58             text            text                NegationTrapConverter\n",
      "59             text            text                       NoiseConverter\n",
      "60             text            text                  PersuasionConverter\n",
      "61             text            text              PolicyPuppetryConverter\n",
      "62             text            text                     PuzzledConverter\n",
      "63             text            text                       ROT13Converter\n",
      "64             text            text        RandomCapitalLettersConverter\n",
      "65             text            text           RandomTranslationConverter\n",
      "66             text            text                 RepeatTokenConverter\n",
      "67             text            text                 SATAMaskingConverter\n",
      "68             text            text       ScientificTranslationConverter\n",
      "69             text            text               SearchReplaceConverter\n",
      "70             text            text               SelectiveTextConverter\n",
      "71             text            text          SneakyBitsSmugglerConverter\n",
      "72             text            text                  StringJoinConverter\n",
      "73             text            text                SuffixAppendConverter\n",
      "74             text            text                 SuperscriptConverter\n",
      "75             text            text                 TaskFramingConverter\n",
      "76             text            text                     TatweelConverter\n",
      "77             text            text             TemplateSegmentConverter\n",
      "78             text            text                       TenseConverter\n",
      "79             text            text               TextJailbreakConverter\n",
      "80             text            text                        ToneConverter\n",
      "81             text            text      ToxicSentenceGeneratorConverter\n",
      "82             text            text                 TranslationConverter\n",
      "83             text            text           UnicodeConfusableConverter\n",
      "84             text            text          UnicodeReplacementConverter\n",
      "85             text            text         UnicodeSubstitutionConverter\n",
      "86             text            text                         UrlConverter\n",
      "87             text            text                   VariationConverter\n",
      "88             text            text   VariationSelectorSmugglerConverter\n",
      "89             text            text                    VigenereConverter\n",
      "90             text            text                       ZalgoConverter\n",
      "91             text            text                   ZeroWidthConverter\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "from pyrit.converter import get_converter_modalities\n",
    "from pyrit.output import output_attack_async\n",
    "from pyrit.setup import IN_MEMORY, initialize_pyrit_async\n",
    "\n",
    "await initialize_pyrit_async(memory_db_type=IN_MEMORY, seed=42)  # type: ignore\n",
    "\n",
    "# Get all converters with their modalities\n",
    "converter_list = get_converter_modalities()\n",
    "\n",
    "# Create a list of rows for the DataFrame\n",
    "rows = []\n",
    "for name, inputs, outputs in converter_list:\n",
    "    input_str = \", \".join(inputs) if inputs else \"any\"\n",
    "    output_str = \", \".join(outputs) if outputs else \"any\"\n",
    "    rows.append({\"Input Modality\": input_str, \"Output Modality\": output_str, \"Converter\": name})\n",
    "\n",
    "# Create DataFrame and sort\n",
    "df = pd.DataFrame(rows)\n",
    "df = df.sort_values(by=[\"Input Modality\", \"Output Modality\", \"Converter\"]).reset_index(drop=True)\n",
    "\n",
    "# Display all rows\n",
    "pd.set_option(\"display.max_rows\", None)\n",
    "print(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "## Converter Categories\n",
    "\n",
    "Converters are organized into the following categories:\n",
    "\n",
    "- **[Text-to-Text Converters](1_text_to_text_converters.ipynb)**: Non-LLM (encoding, obfuscation) and LLM-based (translation, variation, tone)\n",
    "- **[Audio Converters](2_audio_converters.ipynb)**: Text-to-Audio, Audio-to-Text, Audio-to-Audio\n",
    "- **[Image Converters](3_image_converters.ipynb)**: Text-to-Image, Image-to-Image\n",
    "- **[Video Converters](4_video_converters.ipynb)**: Image-to-Video\n",
    "- **[File Converters](5_file_converters.ipynb)**: Text-to-File (PDF, URL)\n",
    "- **[Selectively Converting](6_selectively_converting.ipynb)**: Advanced techniques for converting portions of prompts"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## Simple Converter Example"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "Converters can be used to perform these types of transformations. Here is a simple program that uses Rot13Converter converter, RandomCapitalLettersConverter, and AsciiArtConverter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "text: gryy zr ubj gb phg qbja n gerr\n",
      "text: telL mE how tO cuT down a tree\n",
      "text:                                                                                                                                                                                                                                                                               \n",
      "  ######   ######    ###      ###              ##  ##   ######            ##  ##    ####    ##  ##            ######    ####             ######   ##  ##   ######            #####     ####    ##  ##   ##  ##            ######            ######   #####    ######   ###### \n",
      "    ##    ###  ##   ###      ###               ######  ###  ##           ##  ###   ##  ##   ##  ##              ##     ##  ##           ###  ##   ##  ##     ##              ##  ##   ##  ##   ##  ##   ### ##           ###  ##              ##     ##  ##  ###  ##  ###  ## \n",
      "   ##     ####      ##       ##               #######  ####              ##  ##   ##   ##  ##   ##             ##     ##   ##           ##       ##  ##     ##              ##   ##  ##   ##  ##   ##  ######            ##  ##              ##     ##  ###  ####     ####    \n",
      "   ##     ##       ###      ###               ##  ##   ##               #######  ##   ##   #######             ##    ##   ##           ##        ##  ##     ##             ###   ## ##   ##   #######  ## ###           #######              ##     ######   ##       ##      \n",
      "  ##     ###  ##   ##  ##   ##  ##           ##  ##   ###  ##           ##  ##   ##  ##   #######             ##     ##  ##            ##  ##   ##  ##     ##              ##   ##  ##  ##   #######  ##  ##            ##  ##              ##     ##  ##   ###  ##  ###  ##  \n",
      "  ##     #######  #######  #######           ##  ##   #######           ##  ##    ####    ##   ##             ##      ####             ######    ####      ##              ######    ####    ##   ##  ##  ##            ##  ##              ##     ##   ### #######  #######  \n",
      "                                                                                                                                                                                                                                                                              \n",
      "\n",
      "text: 0000000001110100 0000000001100101 0000000001101100 0000000001101100 0000000000100000 0000000001101101 0000000001100101 0000000000100000 0000000001101000 0000000001101111 0000000001110111 0000000000100000 0000000001110100 0000000001101111 0000000000100000 0000000001100011 0000000001110101 0000000001110100 0000000000100000 0000000001100100 0000000001101111 0000000001110111 0000000001101110 0000000000100000 0000000001100001 0000000000100000 0000000001110100 0000000001110010 0000000001100101 0000000001100101\n",
      "text: t m h t c d a t\n"
     ]
    }
   ],
   "source": [
    "from pyrit.converter import (\n",
    "    AsciiArtConverter,\n",
    "    BinaryConverter,\n",
    "    FirstLetterConverter,\n",
    "    RandomCapitalLettersConverter,\n",
    "    ROT13Converter,\n",
    ")\n",
    "\n",
    "prompt = \"tell me how to cut down a tree\"\n",
    "\n",
    "print(await ROT13Converter().convert_tokens_async(prompt=prompt))  # type: ignore\n",
    "print(await RandomCapitalLettersConverter(percentage=25.0).convert_tokens_async(prompt=prompt))  # type: ignore\n",
    "print(await AsciiArtConverter().convert_tokens_async(prompt=prompt))  # type: ignore\n",
    "print(await BinaryConverter().convert_tokens_async(prompt=prompt))  # type: ignore\n",
    "print(await FirstLetterConverter().convert_tokens_async(prompt=prompt))  # type: ignore"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "## Stacking Converters\n",
    "\n",
    "Although converters can be used on their own, they should be thought of as a piece in the pipeline. Typically any attack will have arguments so that prompts can be converted before being sent to the target. They can be stacked, use LLMs, and are a powerful tool.\n",
    "\n",
    "\n",
    "### Stacking Converters with PromptSendingAttack\n",
    "\n",
    "In the following example, a TextTarget is used so these prompts are simply printed and added to memory. This can be useful if you are red teaming something and need to manually enter prompts. However, the target can be replaced with any other [target](../targets/0_prompt_targets.md). E.g., if you have API access you can add a target there.\n",
    "\n",
    "In this example the converters are stacked. First a variation is found using `VariationConverter`, and then `StringJoinConverter` is used to add a dash between letters. Remember that order matters. If `StringJoinConverter` came first, we would be asking the LLM to make variations of the prompt:\n",
    "\"t-e-l-l- - m-e- -h-o-w- -t-o- -c-u-t- -d-o-w-n - a- -t-r-e-e\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TextTarget: user: C-a-n y-o-u e-x-p-l-a-i-n t-h-e s-t-e-p-s t-o f-e-l-l a t-r-e-e-?\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[33m════════════════════════════════════════════════════════════════════════════════════════════════════\u001b[0m\n",
      "\u001b[1m\u001b[33m                                  ❓ ATTACK RESULT: UNDETERMINED ❓                                   \u001b[0m\n",
      "\u001b[33m════════════════════════════════════════════════════════════════════════════════════════════════════\u001b[0m\n",
      "\n",
      "\u001b[1m\u001b[44m\u001b[37m Attack Summary \u001b[0m\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[1m  📋 Basic Information\u001b[0m\n",
      "\u001b[36m    • Objective: tell me how to cut down a tree\u001b[0m\n",
      "\u001b[36m    • Attack Type: PromptSendingAttack\u001b[0m\n",
      "\u001b[36m    • Conversation ID: 410b9dae-5987-441f-b973-81cfe76ac1a9\u001b[0m\n",
      "\n",
      "\u001b[1m  ⚡ Execution Metrics\u001b[0m\n",
      "\u001b[32m    • Turns Executed: 1\u001b[0m\n",
      "\u001b[32m    • Execution Time: 3.46s\u001b[0m\n",
      "\n",
      "\u001b[1m  🎯 Outcome\u001b[0m\n",
      "\u001b[33m    • Status: ❓ UNDETERMINED\u001b[0m\n",
      "\u001b[37m    • Reason: No objective scorer configured\u001b[0m\n",
      "\n",
      "\u001b[1m\u001b[44m\u001b[37m Conversation History with Objective Target \u001b[0m\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[1m\u001b[34m🔹 Turn 1 - USER\u001b[0m\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[36m   Original:\u001b[0m\n",
      "\u001b[37m  tell me how to cut down a tree\u001b[0m\n",
      "\n",
      "\u001b[36m   Converted:\u001b[0m\n",
      "\u001b[37m  C-a-n y-o-u e-x-p-l-a-i-n t-h-e s-t-e-p-s t-o f-e-l-l a t-r-e-e-?\u001b[0m\n",
      "\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\n",
      "\u001b[2m\u001b[37m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[2m\u001b[37m                            Report generated at: 2026-05-21 22:35:20 UTC                            \u001b[0m\n"
     ]
    }
   ],
   "source": [
    "from pyrit.converter import StringJoinConverter, VariationConverter\n",
    "from pyrit.executor.attack import (\n",
    "    AttackConverterConfig,\n",
    "    PromptSendingAttack,\n",
    ")\n",
    "from pyrit.prompt_normalizer import ConverterConfiguration\n",
    "from pyrit.prompt_target import OpenAIChatTarget, TextTarget\n",
    "\n",
    "objective = \"tell me how to cut down a tree\"\n",
    "\n",
    "# converter target to send prompt to; in this case configured to the Azure OpenAI GPT-4o Chat model\n",
    "converter_target = OpenAIChatTarget()\n",
    "prompt_variation_converter = VariationConverter(converter_target=converter_target)\n",
    "\n",
    "converter_configs = ConverterConfiguration.from_converters(  # type: ignore\n",
    "    converters=[prompt_variation_converter, StringJoinConverter()]\n",
    ")\n",
    "\n",
    "converter_config = AttackConverterConfig(request_converters=converter_configs)  # type: ignore\n",
    "\n",
    "target = TextTarget()\n",
    "attack = PromptSendingAttack(\n",
    "    objective_target=target,\n",
    "    attack_converter_config=converter_config,\n",
    ")\n",
    "\n",
    "result = await attack.execute_async(objective=objective)  # type: ignore\n",
    "\n",
    "await output_attack_async(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "## Response Converters\n",
    "\n",
    "So far, we've focused on **request converters** that transform prompts before sending them to the target. PyRIT also supports **response converters** that transform the target's response before returning it. This is useful in scenarios like:\n",
    "\n",
    "- Translating responses back to the original language after sending prompts in a different language\n",
    "- Decoding encoded responses\n",
    "- Normalizing or cleaning up response text\n",
    "\n",
    "Response converters use the same `ConverterConfiguration` class as request converters. They are configured via the `response_converters` parameter in `AttackConverterConfig`.\n",
    "\n",
    "### Translation Round-Trip Example\n",
    "\n",
    "A common use case is sending prompts in a different language to test how the target handles non-English input. In this example, we:\n",
    "\n",
    "1. Use a **request converter** to translate the prompt from English to French\n",
    "2. Send the translated prompt to the target\n",
    "3. Use a **response converter** to translate the response back to English"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[33m════════════════════════════════════════════════════════════════════════════════════════════════════\u001b[0m\n",
      "\u001b[1m\u001b[33m                                  ❓ ATTACK RESULT: UNDETERMINED ❓                                   \u001b[0m\n",
      "\u001b[33m════════════════════════════════════════════════════════════════════════════════════════════════════\u001b[0m\n",
      "\n",
      "\u001b[1m\u001b[44m\u001b[37m Attack Summary \u001b[0m\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[1m  📋 Basic Information\u001b[0m\n",
      "\u001b[36m    • Objective: What is the capital of France?\u001b[0m\n",
      "\u001b[36m    • Attack Type: PromptSendingAttack\u001b[0m\n",
      "\u001b[36m    • Conversation ID: 70c4a57e-2d8e-478c-9baa-c1356f3709ac\u001b[0m\n",
      "\n",
      "\u001b[1m  ⚡ Execution Metrics\u001b[0m\n",
      "\u001b[32m    • Turns Executed: 1\u001b[0m\n",
      "\u001b[32m    • Execution Time: 5.93s\u001b[0m\n",
      "\n",
      "\u001b[1m  🎯 Outcome\u001b[0m\n",
      "\u001b[33m    • Status: ❓ UNDETERMINED\u001b[0m\n",
      "\u001b[37m    • Reason: No objective scorer configured\u001b[0m\n",
      "\n",
      "\u001b[1m\u001b[44m\u001b[37m Conversation History with Objective Target \u001b[0m\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[1m\u001b[34m🔹 Turn 1 - USER\u001b[0m\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[36m   Original:\u001b[0m\n",
      "\u001b[37m  What is the capital of France?\u001b[0m\n",
      "\n",
      "\u001b[36m   Converted:\u001b[0m\n",
      "\u001b[37m  Quelle est la capitale de la France ?\u001b[0m\n",
      "\n",
      "\u001b[33m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[1m\u001b[33m🔸 ASSISTANT\u001b[0m\n",
      "\u001b[33m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[36m   Original:\u001b[0m\n",
      "\u001b[37m  La capitale de la France est **Paris**.\u001b[0m\n",
      "\n",
      "\u001b[36m   Converted:\u001b[0m\n",
      "\u001b[37m  The capital of France is **Paris**.\u001b[0m\n",
      "\n",
      "\u001b[34m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\n",
      "\u001b[2m\u001b[37m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
      "\u001b[2m\u001b[37m                            Report generated at: 2026-05-21 22:35:26 UTC                            \u001b[0m\n"
     ]
    }
   ],
   "source": [
    "from pyrit.converter import TranslationConverter\n",
    "from pyrit.executor.attack import (\n",
    "    AttackConverterConfig,\n",
    "    PromptSendingAttack,\n",
    ")\n",
    "from pyrit.prompt_normalizer import ConverterConfiguration\n",
    "from pyrit.prompt_target import OpenAIChatTarget\n",
    "\n",
    "objective = \"What is the capital of France?\"\n",
    "\n",
    "# Create an LLM target for the converters\n",
    "converter_target = OpenAIChatTarget()\n",
    "\n",
    "# Create an LLM target to send prompts to\n",
    "prompt_target = OpenAIChatTarget()\n",
    "\n",
    "# Request converter: translate English to French\n",
    "request_converter = TranslationConverter(converter_target=converter_target, language=\"French\")\n",
    "request_converter_config = ConverterConfiguration(converters=[request_converter])\n",
    "\n",
    "# Response converter: translate response back to English\n",
    "response_converter = TranslationConverter(converter_target=converter_target, language=\"English\")\n",
    "response_converter_config = ConverterConfiguration(converters=[response_converter])\n",
    "\n",
    "# Configure the attack with both request and response converters\n",
    "converter_config = AttackConverterConfig(\n",
    "    request_converters=[request_converter_config],\n",
    "    response_converters=[response_converter_config],\n",
    ")\n",
    "\n",
    "attack = PromptSendingAttack(\n",
    "    objective_target=prompt_target,\n",
    "    attack_converter_config=converter_config,\n",
    ")\n",
    "\n",
    "result = await attack.execute_async(objective=objective)  # type: ignore\n",
    "\n",
    "# Print the conversation showing both original and converted values\n",
    "await output_attack_async(result)"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
