<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://petercarragher.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://petercarragher.com/" rel="alternate" type="text/html" /><updated>2026-02-12T14:56:22+00:00</updated><id>http://petercarragher.com/feed.xml</id><title type="html">Peter Carragher</title><subtitle>Peter Carragher&apos;s personal website.</subtitle><author><name>Peter Carragher</name></author><entry><title type="html">Question Asking with LLMs</title><link href="http://petercarragher.com/question-asking/" rel="alternate" type="text/html" title="Question Asking with LLMs" /><published>2025-03-01T00:00:00+00:00</published><updated>2025-03-01T00:00:00+00:00</updated><id>http://petercarragher.com/question-asking</id><content type="html" xml:base="http://petercarragher.com/question-asking/"><![CDATA[<h2 id="notebooklm-summary">NotebookLM Summary</h2>

<p>../assets/audio/notebooklm_question_asking.wav</p>

<h2 id="overview">Overview</h2>

<p>This blog post walks you through the implementation of a question-asking game built using Streamlit. The game is designed to encourage curiosity and critical thinking by allowing users to ask questions about a randomly chosen phenomenon. The teacher (an AI model) provides answers that provoke deeper inquiry. The game also evaluates the quality of the questions asked.</p>

<p>The project is divided into the following sections:</p>
<ol>
  <li><strong>Game Setup and Sidebar Configuration</strong>: Setting up the game parameters and user inputs.</li>
  <li><strong>Phenomenon Selection and Explanation</strong>: Randomly selecting a phenomenon and generating an explanation.</li>
  <li><strong>Conversation Management</strong>: Initializing and managing the AI-driven conversation.</li>
  <li><strong>Evaluation of Questions</strong>: Analyzing the user’s questions based on knowledge graph coverage and Bloom’s Taxonomy.</li>
  <li><strong>User Interface</strong>: Displaying the chat interface and handling user interactions.</li>
</ol>

<p>Here’s a <a href="https://questionme.streamlit.app/">demo on streamlit</a>.</p>

<p>Let’s dive into each section with code snippets and detailed explanations.</p>

<hr />

<h2 id="1-game-setup-and-sidebar-configuration">1. Game Setup and Sidebar Configuration</h2>

<p>The game begins with a sidebar where users can configure the game settings, such as the number of questions and the subject of the phenomenon. This setup ensures that the user has control over the game parameters, making the experience customizable and engaging.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">streamlit</span> <span class="k">as</span> <span class="n">st</span>
<span class="kn">import</span> <span class="nn">phenomena</span>

<span class="n">st</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">"Question Asking Game"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">"Open the sidebar to start a new game."</span><span class="p">)</span>
<span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s">'questions_asked'</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>

<span class="n">subjects</span> <span class="o">=</span> <span class="n">phenomena</span><span class="p">.</span><span class="n">phenomena</span><span class="p">.</span><span class="n">keys</span><span class="p">()</span>
<span class="k">with</span> <span class="n">st</span><span class="p">.</span><span class="n">sidebar</span><span class="p">:</span>
    <span class="n">password</span> <span class="o">=</span> <span class="s">""</span>  <span class="c1"># User password input (currently disabled)
</span>    <span class="n">question_limit</span> <span class="o">=</span> <span class="n">st</span><span class="p">.</span><span class="n">number_input</span><span class="p">(</span><span class="s">'Question Limit'</span><span class="p">,</span> <span class="n">min_value</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">step</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">subject</span> <span class="o">=</span> <span class="n">st</span><span class="p">.</span><span class="n">selectbox</span><span class="p">(</span><span class="s">'Choose a subject'</span><span class="p">,</span> <span class="n">subjects</span><span class="p">,</span> <span class="n">label_visibility</span><span class="o">=</span><span class="s">'collapsed'</span><span class="p">)</span>
</code></pre></div></div>

<p>Here, the <code class="language-plaintext highlighter-rouge">st.sidebar</code> component is used to create a sidebar where users can input their preferences. The <code class="language-plaintext highlighter-rouge">st.number_input</code> and <code class="language-plaintext highlighter-rouge">st.selectbox</code> widgets allow users to set the question limit and select a subject, respectively. These inputs are stored in the session state for use throughout the game.</p>

<hr />

<h2 id="2-phenomenon-selection-and-explanation">2. Phenomenon Selection and Explanation</h2>

<p>Once the game starts, a random phenomenon is selected from the chosen subject. The AI model then generates a high-level explanation of the phenomenon to provide context for the user.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="nn">langchain.chat_models</span> <span class="kn">import</span> <span class="n">ChatOpenAI</span>
<span class="kn">from</span> <span class="nn">langchain.prompts.chat</span> <span class="kn">import</span> <span class="n">ChatPromptTemplate</span>
<span class="kn">from</span> <span class="nn">langchain.chains</span> <span class="kn">import</span> <span class="n">LLMChain</span>

<span class="k">def</span> <span class="nf">get_paradox</span><span class="p">():</span>
    <span class="n">chosen_paradox</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">phenomena</span><span class="p">.</span><span class="n">phenomena</span><span class="p">[</span><span class="n">subject</span><span class="p">])</span>
    <span class="n">chat_prompt</span> <span class="o">=</span> <span class="n">ChatPromptTemplate</span><span class="p">.</span><span class="n">from_messages</span><span class="p">([</span><span class="n">game_description</span><span class="p">,</span> <span class="s">"{text}"</span><span class="p">])</span>
    <span class="n">chain</span> <span class="o">=</span> <span class="n">LLMChain</span><span class="p">(</span><span class="n">llm</span><span class="o">=</span><span class="n">ChatOpenAI</span><span class="p">(),</span> <span class="n">prompt</span><span class="o">=</span><span class="n">chat_prompt</span><span class="p">)</span>
    <span class="n">paradox_explanation</span> <span class="o">=</span> <span class="n">chain</span><span class="p">.</span><span class="n">run</span><span class="p">(</span>
        <span class="sa">f</span><span class="s">"Describe the following phenomena in 2 paragraphs: </span><span class="si">{</span><span class="n">chosen_paradox</span><span class="si">}</span><span class="s">"</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">chosen_paradox</span><span class="p">,</span> <span class="n">paradox_explanation</span><span class="p">,</span> <span class="s">""</span>
</code></pre></div></div>

<p>This function uses the <code class="language-plaintext highlighter-rouge">numpy</code> library to randomly select a phenomenon and the LangChain library to generate an explanation. The explanation is designed to be accessible to a layperson while still being thought-provoking.</p>

<hr />

<h2 id="3-conversation-management">3. Conversation Management</h2>

<p>The core of the game is the conversation between the user (student) and the AI (teacher). This section initializes the conversation with a prompt template that sets the context and rules for the interaction.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">langchain.chains.conversation.memory</span> <span class="kn">import</span> <span class="n">ConversationSummaryMemory</span>
<span class="kn">from</span> <span class="nn">langchain.prompts.prompt</span> <span class="kn">import</span> <span class="n">PromptTemplate</span>
<span class="kn">from</span> <span class="nn">langchain</span> <span class="kn">import</span> <span class="n">OpenAI</span><span class="p">,</span> <span class="n">ConversationChain</span>

<span class="k">def</span> <span class="nf">init_gpt</span><span class="p">(</span><span class="n">game_description</span><span class="p">):</span>
    <span class="n">chosen_paradox</span><span class="p">,</span> <span class="n">paradox_explanation</span><span class="p">,</span> <span class="n">paradox_rdf</span> <span class="o">=</span> <span class="n">get_paradox</span><span class="p">()</span>
    <span class="n">template</span> <span class="o">=</span> <span class="n">game_description</span> <span class="o">+</span> <span class="s">"""
Teacher: Please ask questions about """</span> <span class="o">+</span> <span class="n">chosen_paradox</span> <span class="o">+</span> <span class="s">"""
{history}
Student: {input}
Teacher:"""</span>
    <span class="n">PROMPT</span> <span class="o">=</span> <span class="n">PromptTemplate</span><span class="p">(</span><span class="n">input_variables</span><span class="o">=</span><span class="p">[</span><span class="s">"history"</span><span class="p">,</span> <span class="s">"input"</span><span class="p">],</span> <span class="n">template</span><span class="o">=</span><span class="n">template</span><span class="p">)</span>
    <span class="n">llm</span> <span class="o">=</span> <span class="n">OpenAI</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">model_name</span><span class="o">=</span><span class="s">"gpt-3.5-turbo"</span><span class="p">)</span>
    <span class="n">conversation</span> <span class="o">=</span> <span class="n">ConversationChain</span><span class="p">(</span>
        <span class="n">prompt</span><span class="o">=</span><span class="n">PROMPT</span><span class="p">,</span>
        <span class="n">llm</span><span class="o">=</span><span class="n">llm</span><span class="p">,</span>
        <span class="n">verbose</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
        <span class="n">memory</span><span class="o">=</span><span class="n">ConversationSummaryMemory</span><span class="p">(</span><span class="n">llm</span><span class="o">=</span><span class="n">llm</span><span class="p">,</span> <span class="n">ai_prefix</span><span class="o">=</span><span class="s">"Teacher"</span><span class="p">,</span> <span class="n">human_prefix</span><span class="o">=</span><span class="s">"Student"</span><span class="p">),</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">conversation</span><span class="p">,</span> <span class="n">paradox_explanation</span><span class="p">,</span> <span class="n">paradox_rdf</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ConversationChain</code> object manages the dialogue, using memory to track the conversation history. This ensures that the AI’s responses are coherent and contextually relevant.</p>

<hr />

<h2 id="4-evaluation-of-questions">4. Evaluation of Questions</h2>

<p>At the end of the game, the user’s questions are evaluated based on their depth, coverage, and alignment with Bloom’s Taxonomy. This provides valuable feedback to the user on their questioning skills.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">evaluate</span><span class="p">():</span>
    <span class="n">questions</span> <span class="o">=</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="sa">f</span><span class="s">"Q</span><span class="si">{</span><span class="n">idx</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">. </span><span class="si">{</span><span class="n">q</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">idx</span><span class="p">,</span> <span class="n">q</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">.</span><span class="n">past</span><span class="p">)])</span>
    <span class="n">evaluate_rdf_prompt</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"""Estimate how comprehensive my questions have been...
    Questions:
    </span><span class="si">{</span><span class="n">questions</span><span class="si">}</span><span class="s">"""</span>
    <span class="n">evaluate_blooms_prompt</span> <span class="o">=</span> <span class="s">"""Use Blooms Taxonomy to classify my questions...
    Questions:
    {questions}"""</span>
    <span class="k">with</span> <span class="n">eval_tab</span><span class="p">:</span>
        <span class="n">st</span><span class="p">.</span><span class="n">header</span><span class="p">(</span><span class="s">'Knowledge Graph Coverage'</span><span class="p">)</span>
        <span class="n">kg_coverage</span> <span class="o">=</span> <span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">.</span><span class="n">chain</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">evaluate_rdf_prompt</span><span class="p">)</span>
        <span class="n">st</span><span class="p">.</span><span class="n">markdown</span><span class="p">(</span><span class="n">kg_coverage</span><span class="p">)</span>
        <span class="n">st</span><span class="p">.</span><span class="n">header</span><span class="p">(</span><span class="s">'Blooms Taxonomy'</span><span class="p">)</span>
        <span class="n">blooms_taxonomy</span> <span class="o">=</span> <span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">.</span><span class="n">chain</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">evaluate_blooms_prompt</span><span class="p">)</span>
        <span class="n">st</span><span class="p">.</span><span class="n">markdown</span><span class="p">(</span><span class="n">blooms_taxonomy</span><span class="p">)</span>
</code></pre></div></div>

<p>This function uses prompts to analyze the user’s questions. The results are displayed in the evaluation tab, providing insights into the user’s performance.</p>

<hr />

<h2 id="5-user-interface">5. User Interface</h2>

<p>The user interface is designed to be intuitive and interactive. It displays the conversation and allows the user to input their questions.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">streamlit_chat</span> <span class="kn">import</span> <span class="n">message</span>

<span class="k">with</span> <span class="n">chat_tab</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'generated'</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">response</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">[</span><span class="s">'generated'</span><span class="p">]):</span>
            <span class="k">if</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">3</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">3</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">[</span><span class="s">'past'</span><span class="p">]):</span>
                <span class="n">message</span><span class="p">(</span><span class="n">st</span><span class="p">.</span><span class="n">session_state</span><span class="p">[</span><span class="s">'past'</span><span class="p">][</span><span class="n">i</span><span class="o">-</span><span class="mi">3</span><span class="p">],</span> <span class="n">is_user</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">i</span><span class="o">-</span><span class="mi">3</span><span class="si">}</span><span class="s">_user"</span><span class="p">)</span>
            <span class="n">message</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">password</span> <span class="o">==</span> <span class="n">pwd</span><span class="p">:</span>
        <span class="n">st</span><span class="p">.</span><span class="n">text_input</span><span class="p">(</span><span class="s">"Ask a question:"</span><span class="p">,</span> <span class="n">on_change</span><span class="o">=</span><span class="n">on_input_change</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s">"user_input"</span><span class="p">)</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">streamlit_chat</code> library is used to render the chat interface. The <code class="language-plaintext highlighter-rouge">st.text_input</code> widget captures the user’s questions, which are then processed by the AI.</p>

<hr />

<h2 id="conclusion">Conclusion</h2>

<p>This project demonstrates how to build an interactive question-asking game using Streamlit and LangChain. The game encourages curiosity and critical thinking while providing insightful evaluations of the user’s questions. By combining modern AI tools with an engaging interface, this project showcases the potential of educational technology.</p>

<p>Find the complete code on <a href="https://github.com/PeterCarragher/question_asking">GitHub</a> and a demo on <a href="https://questionme.streamlit.app/">Streamlit</a></p>

<p><img src="/assets/images/question_asking_eval.png" alt="Evaluation Tab" /></p>]]></content><author><name>Peter Carragher</name></author><summary type="html"><![CDATA[Evaluating Question Asking Ability with LLMs]]></summary></entry><entry><title type="html">Conversational AI on YouTube</title><link href="http://petercarragher.com/dig-deeper/" rel="alternate" type="text/html" title="Conversational AI on YouTube" /><published>2025-02-01T00:00:00+00:00</published><updated>2025-02-01T00:00:00+00:00</updated><id>http://petercarragher.com/dig-deeper</id><content type="html" xml:base="http://petercarragher.com/dig-deeper/"><![CDATA[<h2 id="notebooklm-summary">NotebookLM Summary</h2>

<p>../assets/audio/notebooklm_dig_deeper.wav</p>

<h2 id="overview">Overview</h2>
<p>This blog post explores the creation of a prototype browser plugin that analyzes the YouTube Shorts you watch. The goal is to encourage users to actively search and “dig deeper” into topics, rather than passively consuming content from the platform’s recommendation system. The plugin achieves this by generating transcripts of videos, feeding them to a language model, and providing a chat interface for interaction.</p>

<p>The plugin has three main components:</p>

<ol>
  <li><strong>Transcripts</strong>: Extracting audio from YouTube videos and converting it into text.</li>
  <li><strong>GPT API Integration</strong>: Using OpenAI’s GPT to generate curiosity-provoking suggestions based on the video content.</li>
  <li><strong>Chat Interface</strong>: A simple browser-based chatbox for user interaction.</li>
</ol>

<h2 id="transcripts">Transcripts</h2>
<p>The first step is to extract audio from YouTube videos and convert it into text using OpenAI’s Whisper model. Below is the code for downloading audio, converting it to a suitable format, and generating transcripts.</p>

<h3 id="downloading-audio">Downloading Audio</h3>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">ytdl</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@distube/ytdl-core</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">ytdl-core</code> library is used to download audio from YouTube. The <code class="language-plaintext highlighter-rouge">downloadAudioOnly</code> function filters the video to download only the audio stream and saves it to a specified path.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">downloadAudioOnly</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">audio_path</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">stream</span> <span class="o">=</span> <span class="nx">ytdl</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> 
    <span class="p">{</span>
      <span class="na">filter</span><span class="p">:</span> <span class="dl">'</span><span class="s1">audioonly</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">requestOptions</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
          <span class="dl">'</span><span class="s1">User-Agent</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36</span><span class="dl">'</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">);</span>

  <span class="nx">stream</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">createWriteStream</span><span class="p">(</span><span class="nx">audio_path</span><span class="p">))</span>
    <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">finish</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">callback</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Download and save completed</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">callback</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error writing to file: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">err</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">});</span>

  <span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">callback</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error downloading audio: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">err</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This function ensures that only the audio is downloaded, which reduces the file size and processing time.</p>

<h3 id="converting-audio-to-wav-format">Converting Audio to WAV Format</h3>
<p>Once the audio is downloaded, it needs to be converted to a WAV format compatible with the Whisper model. This is done using the <code class="language-plaintext highlighter-rouge">ffmpeg-static</code> library.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">convertMP3ToWAV</span><span class="p">(</span><span class="nx">audio_path_mp3</span><span class="p">,</span> <span class="nx">audio_path_wav</span><span class="p">){</span>
  <span class="kd">const</span> <span class="nx">ffmpeg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">ffmpeg-static</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">spawn</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">child_process</span><span class="dl">'</span><span class="p">).</span><span class="nx">spawn</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">ffmpegProcess</span> <span class="o">=</span> <span class="nx">spawn</span><span class="p">(</span><span class="nx">ffmpeg</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">-i</span><span class="dl">'</span><span class="p">,</span> <span class="nx">audio_path_mp3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">-ar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">16000</span><span class="dl">'</span><span class="p">,</span> <span class="nx">audio_path_wav</span><span class="p">]);</span>

  <span class="nx">ffmpegProcess</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">exit</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">Audio converted successfully!</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">});</span>

  <span class="nx">ffmpegProcess</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error converting audio:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This function uses <code class="language-plaintext highlighter-rouge">ffmpeg</code> to convert the MP3 file to a WAV file with a sample rate of 16,000 Hz, which is required for Whisper.</p>

<h3 id="generating-transcripts">Generating Transcripts</h3>
<p>The <code class="language-plaintext highlighter-rouge">ytTranscript</code> function orchestrates the process of downloading audio, converting it to WAV, and generating a transcript using Whisper.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">ytTranscript</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">video_id</span> <span class="o">=</span> <span class="nx">get_video_id</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
    <span class="nx">audio_path_mp3</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">(),</span> <span class="dl">'</span><span class="s1">output/</span><span class="dl">'</span><span class="p">,</span> <span class="nx">video_id</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.mp3</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">audio_path_wav</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">(),</span> <span class="dl">'</span><span class="s1">output/</span><span class="dl">'</span><span class="p">,</span> <span class="nx">video_id</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.wav</span><span class="dl">'</span><span class="p">);</span>

    <span class="nx">downloadAudioOnly</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">audio_path_mp3</span><span class="p">,</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">message</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error downloading or saving the video:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
        <span class="nx">reject</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
        <span class="nx">convertMP3ToWAV</span><span class="p">(</span><span class="nx">audio_path_mp3</span><span class="p">,</span> <span class="nx">audio_path_wav</span><span class="p">);</span>
        <span class="kd">const</span> <span class="nx">whisper</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">whisper-node</span><span class="dl">'</span><span class="p">);</span>

        <span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">try</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">transcript</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">whisper</span><span class="p">.</span><span class="nx">whisper</span><span class="p">(</span><span class="nx">audio_path_wav</span><span class="p">,</span> <span class="p">{</span><span class="na">modelName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">base.en</span><span class="dl">"</span><span class="p">,</span> <span class="na">word_timestamps</span><span class="p">:</span> <span class="kc">false</span><span class="p">});</span>
            <span class="kd">const</span> <span class="nx">speechText</span> <span class="o">=</span> <span class="nx">transcript</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="nx">item</span><span class="p">.</span><span class="nx">speech</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">);</span>
            <span class="kd">const</span> <span class="nx">cleanedSpeechText</span> <span class="o">=</span> <span class="nx">speechText</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/ </span><span class="se">([</span><span class="sr">',.!?</span><span class="se">])</span><span class="sr">/g</span><span class="p">,</span> <span class="dl">'</span><span class="s1">$1</span><span class="dl">'</span><span class="p">);</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="nx">cleanedSpeechText</span><span class="p">);</span>
            <span class="nx">resolve</span><span class="p">(</span><span class="nx">cleanedSpeechText</span><span class="p">);</span>
          <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
            <span class="nx">reject</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
          <span class="p">}</span>
        <span class="p">})();</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This function:</p>
<ol>
  <li>Downloads the audio from the YouTube video.</li>
  <li>Converts the audio to WAV format.</li>
  <li>Uses Whisper to generate a transcript of the audio.</li>
</ol>

<h2 id="gpt-api-integration">GPT API Integration</h2>
<p>The next step is to integrate OpenAI’s GPT API to generate curiosity-provoking suggestions based on the video transcript. The following code sets up an Express server to handle requests.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">OpenAI</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">openai</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">;</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">limit</span><span class="p">:</span> <span class="dl">'</span><span class="s1">10mb</span><span class="dl">'</span> <span class="p">}));</span>

<span class="kd">const</span> <span class="nx">openai</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OpenAI</span><span class="p">({</span>
  <span class="na">apiKey</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">OPENAI_KEY</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">});</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/oracle</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">query</span><span class="p">,</span> <span class="nx">page</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>

  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">completion</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">openai</span><span class="p">.</span><span class="nx">chat</span><span class="p">.</span><span class="nx">completions</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
      <span class="na">model</span><span class="p">:</span> <span class="dl">'</span><span class="s1">gpt-4o</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">messages</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span> <span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">system</span><span class="dl">'</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="dl">'</span><span class="s1">You are a fact-finding curiosity machine...</span><span class="dl">'</span> <span class="p">},</span>
        <span class="p">{</span> <span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">user</span><span class="dl">'</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="s2">`Context: </span><span class="p">${</span><span class="nx">context</span><span class="p">}</span><span class="s2">, Question: </span><span class="p">${</span><span class="nx">query</span><span class="p">}</span><span class="s2">`</span> <span class="p">},</span>
      <span class="p">],</span>
    <span class="p">});</span>

    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">completion</span><span class="p">.</span><span class="nx">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">message</span><span class="p">.</span><span class="nx">content</span><span class="p">;</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="nx">response</span> <span class="p">});</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error querying OpenAI:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Failed to get a response from OpenAI</span><span class="dl">'</span> <span class="p">});</span>
  <span class="p">}</span>
<span class="p">});</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Oracle server listening at http://localhost:</span><span class="p">${</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>This server:</p>
<ol>
  <li>Accepts a query and video URL from the client.</li>
  <li>Retrieves the transcript for the video.</li>
  <li>Sends the transcript and query to GPT to generate a response.</li>
  <li>Returns the response to the client.</li>
</ol>

<h2 id="chat-interface">Chat Interface</h2>
<p>Finally, a simple chat interface is implemented in the browser to allow users to interact with the plugin.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">send</span><span class="dl">"</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">query</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">input</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="p">;</span>
  <span class="nx">chrome</span><span class="p">.</span><span class="nx">tabs</span><span class="p">.</span><span class="nx">query</span><span class="p">({</span> <span class="na">active</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">currentWindow</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="p">(</span><span class="nx">tabs</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">chrome</span><span class="p">.</span><span class="nx">tabs</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span>
      <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">*://*.youtube.com/*</span><span class="dl">"</span><span class="p">]</span> <span class="p">},</span>
      <span class="k">async</span> <span class="p">(</span><span class="nx">youtubeTabs</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">youtubeTabs</span><span class="p">.</span><span class="nx">length</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">No YouTube tab found</span><span class="dl">"</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="kd">const</span> <span class="nx">currentTab</span> <span class="o">=</span> <span class="nx">youtubeTabs</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
          <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://localhost:3000/oracle</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
            <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
            <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
              <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">query</span><span class="p">,</span> <span class="na">page</span><span class="p">:</span> <span class="nx">currentTab</span><span class="p">.</span><span class="nx">url</span> <span class="p">}),</span>
          <span class="p">})</span>
            <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
            <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
              <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">response</span><span class="p">;</span>
              <span class="kd">const</span> <span class="nx">chatDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">chat</span><span class="dl">"</span><span class="p">);</span>
              <span class="nx">chatDiv</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">+=</span> <span class="s2">`&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; </span><span class="p">${</span><span class="nx">query</span><span class="p">}</span><span class="s2">&lt;/p&gt;`</span><span class="p">;</span>
              <span class="nx">chatDiv</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">+=</span> <span class="s2">`&lt;p&gt;&lt;strong&gt;Oracle:&lt;/strong&gt; </span><span class="p">${</span><span class="nx">response</span><span class="p">}</span><span class="s2">&lt;/p&gt;`</span><span class="p">;</span>
              <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">input</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
              <span class="nx">chatDiv</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">=</span> <span class="nx">chatDiv</span><span class="p">.</span><span class="nx">scrollHeight</span><span class="p">;</span>
            <span class="p">})</span>
            <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
              <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
            <span class="p">});</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">);</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>
<p>This script:</p>
<ol>
  <li>Captures user input from a chatbox.</li>
  <li>Sends the query and current YouTube video URL to the server.</li>
  <li>Displays the response from GPT in the chatbox.</li>
</ol>

<h2 id="conclusion">Conclusion</h2>
<p>While the prototype demonstrates how AI can be used to encourage curiosity and deeper exploration of topics, there are so many limitations:</p>
<ol>
  <li>The chatbox isn’t persistent</li>
  <li>It requires prompting</li>
  <li>It doesn’t interrupt the user with suggestions</li>
  <li>It relies on ytdl, which can be rate limited</li>
  <li>It requires running OpenAI’s whisper model locally</li>
  <li>It requires an unreasonable degree of permission and access to user data in the browser</li>
  <li>There is a fair amount of latency when waiting for responses</li>
</ol>

<p>Despite all that, it’s pretty neat that you can run Whisper locally on a CPU and have it chat to you about what you watch. It looks like this:
<img src="/assets/images/dig_deeper.png" alt="The plugin in-action" title="An example of digging deeper with AI" /></p>

<p>For the full code, checkout this <a href="https://github.com/PeterCarragher/dig_deeper">Github repository</a>.</p>]]></content><author><name>Peter Carragher</name></author><summary type="html"><![CDATA[Getting GPT to chat with you about the content you watch, and provoke curiousity to dig deeper.]]></summary></entry><entry><title type="html">Animating diffusion processes in networks</title><link href="http://petercarragher.com/diffusion-animator/" rel="alternate" type="text/html" title="Animating diffusion processes in networks" /><published>2025-01-01T00:00:00+00:00</published><updated>2025-01-01T00:00:00+00:00</updated><id>http://petercarragher.com/diffusion-animator</id><content type="html" xml:base="http://petercarragher.com/diffusion-animator/"><![CDATA[<h2 id="notebooklm-summary">NotebookLM Summary</h2>

<p>../assets/audio/notebooklm_network_diffusion.wav</p>

<h2 id="overview">Overview</h2>
<ol>
  <li><a href="#ndlib-at-a-glance">NDlib at a Glance</a></li>
  <li><a href="#generating-diffusion-plots-per-timestep">Generating Diffusion Plots Per Timestep</a></li>
  <li><a href="#funcanimation-to-animate-plots-over-time">FuncAnimation to Animate Plots Over Time</a></li>
  <li><a href="#ndlib-model-and-network-configuration">NDlib Model and Network Configuration</a></li>
  <li><a href="#observing-code-updates-in-the-widget">Observing Code Updates in the Widget</a></li>
  <li><a href="#voila-webapp">Voila Webapp</a></li>
</ol>

<h2 id="ndlib-at-a-glance">NDlib at a Glance</h2>

<p><a href="https://ndlib.readthedocs.io/">NDlib</a> is a Python library designed for modeling and simulating diffusion processes over complex networks. It provides a variety of epidemic and information diffusion models, such as SIR, SIS, and Independent Cascade, which can be applied to networks created using libraries like NetworkX. NDlib simplifies the process of configuring, running, and visualizing these models, making it an excellent tool for researchers and educators.</p>

<p>For example, here’s how we define a simple SIR model using NDlib:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">networkx</span> <span class="k">as</span> <span class="n">nx</span>
<span class="kn">import</span> <span class="nn">ndlib.models.ModelConfig</span> <span class="k">as</span> <span class="n">mc</span>
<span class="kn">import</span> <span class="nn">ndlib.models.epidemics</span> <span class="k">as</span> <span class="n">ep</span>

<span class="c1"># Create a graph
</span><span class="n">g</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">erdos_renyi_graph</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">)</span>

<span class="c1"># Define the SIR model
</span><span class="n">model</span> <span class="o">=</span> <span class="n">ep</span><span class="p">.</span><span class="n">SIRModel</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>

<span class="c1"># Configure the model parameters
</span><span class="n">cfg</span> <span class="o">=</span> <span class="n">mc</span><span class="p">.</span><span class="n">Configuration</span><span class="p">()</span>
<span class="n">cfg</span><span class="p">.</span><span class="n">add_model_parameter</span><span class="p">(</span><span class="s">'beta'</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">)</span>  <span class="c1"># Infection rate
</span><span class="n">cfg</span><span class="p">.</span><span class="n">add_model_parameter</span><span class="p">(</span><span class="s">'gamma'</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># Recovery rate
</span><span class="n">cfg</span><span class="p">.</span><span class="n">add_model_parameter</span><span class="p">(</span><span class="s">"fraction_infected"</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">)</span>  <span class="c1"># Initial infected fraction
</span><span class="n">model</span><span class="p">.</span><span class="n">set_initial_status</span><span class="p">(</span><span class="n">cfg</span><span class="p">)</span>

<span class="c1"># Run the model for a number of timesteps
</span><span class="n">iterations</span> <span class="o">=</span> <span class="n">model</span><span class="p">.</span><span class="n">iteration_bunch</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
</code></pre></div></div>
<hr />

<h2 id="generating-diffusion-plots-per-timestep">Generating Diffusion Plots Per Timestep</h2>

<p>NDlib provides tools to visualize the diffusion process over time. For example, we can use the <code class="language-plaintext highlighter-rouge">DiffusionTrend</code> class to plot the number of susceptible, infected, and recovered nodes at each timestep.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ndlib.viz.mpl.DiffusionTrend</span> <span class="kn">import</span> <span class="n">DiffusionTrend</span>

<span class="c1"># Generate trends from the model's iterations
</span><span class="n">trends</span> <span class="o">=</span> <span class="n">model</span><span class="p">.</span><span class="n">build_trends</span><span class="p">(</span><span class="n">iterations</span><span class="p">)</span>

<span class="c1"># Visualize the trends
</span><span class="n">viz</span> <span class="o">=</span> <span class="n">DiffusionTrend</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">trends</span><span class="p">)</span>
<span class="n">viz</span><span class="p">.</span><span class="n">plot</span><span class="p">()</span>
</code></pre></div></div>
<p>This produces a static plot showing how the diffusion evolves over time.</p>

<hr />

<h2 id="funcanimation-to-animate-plots-over-time">FuncAnimation to Animate Plots Over Time</h2>

<p>To make the diffusion process more interactive, we use Matplotlib’s <code class="language-plaintext highlighter-rouge">FuncAnimation</code> to animate the network’s state over time. Each node is colored based on its status (e.g., blue for susceptible, red for infected, green for recovered).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="nn">matplotlib.animation</span> <span class="kn">import</span> <span class="n">FuncAnimation</span>

<span class="c1"># Define node colors based on their status
</span><span class="k">def</span> <span class="nf">get_node_colors</span><span class="p">(</span><span class="n">status</span><span class="p">):</span>
    <span class="n">colors</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">status</span><span class="p">.</span><span class="n">keys</span><span class="p">():</span>
        <span class="k">if</span> <span class="n">status</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="n">colors</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">'blue'</span><span class="p">)</span>  <span class="c1"># Susceptible
</span>        <span class="k">elif</span> <span class="n">status</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
            <span class="n">colors</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">'red'</span><span class="p">)</span>  <span class="c1"># Infected
</span>        <span class="k">elif</span> <span class="n">status</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
            <span class="n">colors</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">'green'</span><span class="p">)</span>  <span class="c1"># Recovered
</span>    <span class="k">return</span> <span class="n">colors</span>

<span class="c1"># Prepare the animation
</span><span class="n">fig</span><span class="p">,</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">subplots</span><span class="p">()</span>
<span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">spring_layout</span><span class="p">(</span><span class="n">g</span><span class="p">)</span>
<span class="n">nodes</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">draw_networkx_nodes</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">node_color</span><span class="o">=</span><span class="n">get_node_colors</span><span class="p">(</span><span class="n">iterations</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="s">'status'</span><span class="p">]),</span> <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">)</span>
<span class="n">edges</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">draw_networkx_edges</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="n">frame</span><span class="p">):</span>
    <span class="n">current_state</span> <span class="o">=</span> <span class="n">iterations</span><span class="p">[</span><span class="n">frame</span><span class="p">][</span><span class="s">'status'</span><span class="p">]</span>
    <span class="n">nodes</span><span class="p">.</span><span class="n">set_facecolor</span><span class="p">(</span><span class="n">get_node_colors</span><span class="p">(</span><span class="n">current_state</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">nodes</span><span class="p">,</span>

<span class="n">animation</span> <span class="o">=</span> <span class="n">FuncAnimation</span><span class="p">(</span><span class="n">fig</span><span class="p">,</span> <span class="n">update</span><span class="p">,</span> <span class="n">frames</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">iterations</span><span class="p">),</span> <span class="n">interval</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">blit</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p>For a basic random graph as in the example above, you should get something like this:
<img src="/assets/images/diffusion_animation.gif" alt="" title="An example of animated network diffusion." /></p>

<h2 id="ndlib-model-and-network-configuration">NDlib Model and Network Configuration</h2>
<p>To make the simulation interactive, we use Jupyter widgets. Users can modify the model’s parameters and the number of timesteps using a text area and a slider.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">ipywidgets</span> <span class="k">as</span> <span class="n">widgets</span>
<span class="kn">from</span> <span class="nn">IPython.display</span> <span class="kn">import</span> <span class="n">display</span>

<span class="c1"># Text area for modifying the model code
</span><span class="n">code_input</span> <span class="o">=</span> <span class="n">widgets</span><span class="p">.</span><span class="n">Textarea</span><span class="p">(</span>
    <span class="n">value</span><span class="o">=</span><span class="s">"""def get_iterations(g, time_steps):
    model = ep.SIRModel(g)
    cfg = mc.Configuration()
    cfg.add_model_parameter('beta', 0.5)
    cfg.add_model_parameter('gamma', 0.1)
    cfg.add_model_parameter("fraction_infected", 0.2)
    model.set_initial_status(cfg)
    return model.iteration_bunch(time_steps), model
"""</span><span class="p">,</span>
    <span class="n">layout</span><span class="o">=</span><span class="n">widgets</span><span class="p">.</span><span class="n">Layout</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s">'100%'</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="s">'300px'</span><span class="p">)</span>
<span class="p">)</span>

<span class="c1"># Slider for adjusting the number of timesteps
</span><span class="n">time_steps_slider</span> <span class="o">=</span> <span class="n">widgets</span><span class="p">.</span><span class="n">IntSlider</span><span class="p">(</span>
    <span class="n">value</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span>
    <span class="nb">min</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
    <span class="nb">max</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span>
    <span class="n">step</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
    <span class="n">description</span><span class="o">=</span><span class="s">'Time Steps:'</span>
<span class="p">)</span>

<span class="c1"># Display the widgets
</span><span class="n">display</span><span class="p">(</span><span class="n">code_input</span><span class="p">)</span>
<span class="n">display</span><span class="p">(</span><span class="n">time_steps_slider</span><span class="p">)</span>
</code></pre></div></div>

<p>These widgets enable users to experiment with different network configurations and model parameters without modifying the underlying code.</p>

<hr />

<h2 id="observing-code-updates-in-the-widget">Observing Code Updates in the Widget</h2>

<p>The line <code class="language-plaintext highlighter-rouge">code_input.observe(run_code, names='value')</code> is used to observe changes in the <code class="language-plaintext highlighter-rouge">code_input</code> widget. Whenever the user modifies the code in the text area, the <code class="language-plaintext highlighter-rouge">run_code</code> function is triggered. This is necessary because:</p>

<ol>
  <li><strong>Dynamic Updates:</strong> It allows the application to dynamically re-run the simulation whenever the user changes the code or parameters.</li>
  <li><strong>Interactivity:</strong> Without this observation, the user would need to manually trigger the simulation, reducing the interactivity of the application.</li>
  <li><strong>Error Handling:</strong> The <code class="language-plaintext highlighter-rouge">run_code</code> function can handle errors in the user-provided code, ensuring the application remains responsive even if invalid code is entered.</li>
</ol>

<p>Here’s how the observation is set up:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">run_code</span><span class="p">(</span><span class="n">change</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">output</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">clear_output</span><span class="p">(</span><span class="n">wait</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
            <span class="n">local_vars</span> <span class="o">=</span> <span class="p">{}</span>
            <span class="k">exec</span><span class="p">(</span><span class="n">change</span><span class="p">[</span><span class="s">'new'</span><span class="p">],</span> <span class="nb">globals</span><span class="p">(),</span> <span class="n">local_vars</span><span class="p">)</span>
            <span class="n">get_iterations</span> <span class="o">=</span> <span class="n">local_vars</span><span class="p">[</span><span class="s">'get_iterations'</span><span class="p">]</span>
            <span class="n">iterations</span><span class="p">,</span> <span class="n">model</span> <span class="o">=</span> <span class="n">get_iterations</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">time_steps_slider</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
            <span class="c1"># Additional logic for updating plots and animations...
</span>        <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"Error executing code:"</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>

<span class="c1"># Observe changes in the code input
</span><span class="n">code_input</span><span class="p">.</span><span class="n">observe</span><span class="p">(</span><span class="n">run_code</span><span class="p">,</span> <span class="n">names</span><span class="o">=</span><span class="s">'value'</span><span class="p">)</span>
</code></pre></div></div>

<p>This ensures that the application reacts immediately to user input, providing a seamless interactive experience.</p>

<hr />

<h2 id="voila-webapp">Voila Webapp</h2>

<p>Finally, we use <a href="https://voila.readthedocs.io/">Voila</a> to convert the notebook into a standalone web application. Voila renders the notebook as an interactive dashboard, hiding the code cells and displaying only the widgets, plots, and animations. This makes it easy to share the application with others, even if they don’t have Python or Jupyter installed.</p>

<p>To run the web app, simply execute the following command in the terminal:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>voila <span class="o">[</span>network_animation.ipynb]<span class="o">(</span>https://github.com/PeterCarragher/diffusion_animator/blob/master/network_animation.ipynb<span class="o">)</span>
</code></pre></div></div>
<p>This will launch a browser window where users can interact with the diffusion simulation, modify parameters, and observe the results in real-time.
<img src="/assets/images/voila_demo.png" alt="The notebook in-action" title="The final voila webapp." /></p>

<hr />

<p>By combining NDlib, Matplotlib, Jupyter widgets, and Voila, this notebook provides a powerful and interactive tool for visualizing diffusion processes in networks. It serves as both an educational resource and a platform for experimentation. For the full code, checkout this <a href="https://github.com/PeterCarragher/diffusion_animator/">Github repository</a>.</p>]]></content><author><name>Peter Carragher</name></author><summary type="html"><![CDATA[A visual learning aid for experimenting with diffusion models and network configurations.]]></summary></entry></feed>