#4469 Prime Study with Sin and Cos

#4469 Prime Study with Sin and Cos

#4469 I want to create an app that takes as input a list of integers and plots Sin(n Pi) for each integer n in the list

If you plot $y = \sin(n\pi)$ for any true integer $n$, you are going to get a shockingly consistent—and potentially very boring—result.

Because $\sin(\pi) = 0$, $\sin(2\pi) = 0$, $\sin(3\pi) = 0$, and so on, the output for every single integer will always be exactly 0.

If your goal is to build the app anyway (perhaps as a learning exercise or a baseline for more complex math plots), here is how you can build a quick, clean interactive app using Python and Streamlit (for the web interface) along with Matplotlib (for the plotting).

The Code (app.py)

Python
import streamlit as st
import matplotlib.pyplot as plt
import numpy as np

# App title and description
st.title("The Zero-Generator Plotter")
st.write("Enter a list of integers to plot $y = \sin(n\pi)$.")

# User input: comma-separated integers
user_input = st.text_input("Enter integers separated by commas:", "1, 2, 3, 4, 5")

try:
    # Parse the input string into a list of integers
    n_values = [int(x.strip()) for x in user_input.split(",") if x.strip()]
    
    if n_values:
        # Calculate Sin(n * pi)
        # We use np.round because floating-point math can sometimes yield tiny numbers like 1e-16
        y_values = [np.round(np.sin(n * np.pi), 10) for n in n_values]
        
        # Create the plot
        fig, ax = plt.subplots()
        ax.scatter(n_values, y_values, color="red", zorder=5, label=r"$\sin(n\pi)$")
        ax.plot(n_values, y_values, linestyle="--", color="blue", alpha=0.5)
        
        # Formatting the plot
        ax.set_xlabel("n (Integers)")
        ax.set_ylabel("y")
        ax.set_title(r"Plot of $y = \sin(n\pi)$")
        ax.grid(True, linestyle=":", alpha=0.6)
        ax.axhline(0, color='black', linewidth=1) # Highlight the x-axis
        
        # Display the plot in the Streamlit app
        st.pyplot(fig)
        
        # Show the raw data table
        st.write("### Data Table")
        st.write(dict(zip(n_values, y_values)))
        
except ValueError:
    st.error("Please ensure your list contains only integers separated by commas.")

How to Run It

  1. Install the requirements (if you haven't already):

    Bash
    pip install streamlit matplotlib numpy
    
  2. Save the code above into a file named app.py.

  3. Run the app from your terminal:

    Bash
    streamlit run app.py
    

💡 A Quick Suggestion: If you want a more interesting curve, try changing the formula in the code to something like $y = \sin(n)$ (without the $\pi$), or let the user input float numbers instead of strictly integers!

What kind of app framework or language were you originally planning to use for this?


What would n Sin(Pi)?

Since $\sin(\pi) = 0$, multiplying it by any number $n$ will always result in $0$.

$$n \sin(\pi) = n \times 0 = 0$$

Regardless of what value $n$ is (an integer, a decimal, or any real number), the final result will always be $0$.


I want a sin wave with wavelength equal to each integer value in the list

That changes things completely! Instead of a string of zeros, you'll get a beautiful set of distinct waves, each stretching out to match the length of its corresponding integer.

For a sine wave to have a wavelength ($\lambda$) equal to $n$, the formula for the wave as a function of $x$ is:

$$y = \sin\left(\frac{2\pi}{\lambda} x\right) \Rightarrow y = \sin\left(\frac{2\pi}{n} x\right)$$

Here is the updated Streamlit app code. It takes your list of integers and overlays the continuous sine waves so you can compare their different wavelengths visually.

The Code (app.py)

Python
import streamlit as st
import matplotlib.pyplot as plt
import numpy as np

st.title("Variable Wavelength Sine Visualizer")
st.write(r"Plots sine waves where the wavelength $\lambda$ equals each integer $n$: $y = \sin\left(\frac{2\pi}{n}x\right)$")

# User input: comma-separated integers
user_input = st.text_input("Enter wavelengths (integers) separated by commas:", "2, 4, 6")

try:
    # Parse the input string into a list of integers
    wavelengths = [int(x.strip()) for x in user_input.split(",") if x.strip()]
    
    # Filter out 0 to prevent division by zero errors
    wavelengths = [w for w in wavelengths if w != 0]
    
    if wavelengths:
        # Determine the x-range dynamically based on the maximum wavelength
        max_w = max(wavelengths)
        x = np.linspace(0, max_w * 2, 1000)  # Plots out to 2 full cycles of the largest wave
        
        # Create the plot
        fig, ax = plt.subplots(figsize=(10, 5))
        
        for w in wavelengths:
            # Formula for wave with wavelength 'w'
            y = np.sin((2 * np.pi / w) * x)
            ax.plot(x, y, label=r"$\lambda$ = " + f"{w}")
        
        # Formatting the plot
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.set_title("Overlay of Sine Waves by Wavelength")
        ax.axhline(0, color='black', linewidth=0.8, linestyle="--")
        ax.grid(True, linestyle=":", alpha=0.6)
        ax.legend(loc="upper right")
        
        # Display the plot in the Streamlit app
        st.pyplot(fig)
        
except ValueError:
    st.error("Please ensure your list contains only non-zero integers separated by commas.")

How this works mathematically:

  • If $n = 2$, the wave completes one full cycle ($0$ to $0$ after going up and down) exactly at $x = 2$.

  • If $n = 6$, the wave stretches out and completes its full cycle at $x = 6$.

If you run this with the updated code, you'll see exactly how changing the integer scales the physical width of the wave.


Redo this for HTML, JavaScript and CSS with a text input field, a submit and clear button, a parser of that text field to produce the list of integers, and a plot routine to plot the equations overlayed on the canvas.

Here is a complete, self-contained solution using vanilla HTML, JavaScript, and CSS. It uses the native HTML5 <canvas> element for lightweight, responsive rendering without needing any external library installations.

You can save this code as a single file (e.g., index.html) and open it directly in any web browser.

The Code (index.html)

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Wavelength Sine Visualizer</title>
    <style>
        :root {
            --bg-color: #f4f7f6;
            --card-bg: #ffffff;
            --text-color: #333333;
            --primary-color: #2563eb;
            --danger-color: #dc2626;
            --border-color: #cccccc;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            background-color: var(--card-bg);
            padding: 24px;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 800px;
        }

        h1 {
            margin-top: 0;
            font-size: 1.5rem;
            color: #111827;
        }

        p {
            color: #6b7280;
            font-size: 0.95rem;
            margin-bottom: 20px;
        }

        .input-group {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }

        input[type="text"] {
            flex: 1;
            min-width: 200px;
            padding: 10px;
            border: 1px solid var(--border-color);
            border-radius: 4px;
            font-size: 1rem;
        }

        button {
            padding: 10px 20px;
            font-size: 1rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: 500;
            transition: background-color 0.2s;
        }

        .btn-submit {
            background-color: var(--primary-color);
            color: white;
        }

        .btn-submit:hover {
            background-color: #1d4ed8;
        }

        .btn-clear {
            background-color: #e5e7eb;
            color: #374151;
        }

        .btn-clear:hover {
            background-color: #d1d5db;
        }

        .error-message {
            color: var(--danger-color);
            font-size: 0.875rem;
            margin-top: -10px;
            margin-bottom: 15px;
            display: none;
        }

        .canvas-container {
            width: 100%;
            position: relative;
        }

        canvas {
            border: 1px solid var(--border-color);
            border-radius: 4px;
            width: 100%;
            height: auto;
            background-color: #fafafa;
        }
    </style>
</head>
<body>

<div class="container">
    <h1>Variable Wavelength Sine Visualizer</h1>
    <p>Enter a comma-separated list of integers. Each integer represents the wavelength (λ) of a sine wave: <em>y = sin((2π / λ) * x)</em></p>
    
    <div class="input-group">
        <input type="text" id="wavelengthInput" value="2, 4, 6" placeholder="e.g., 2, 4, 6">
        <button class="btn-submit" id="submitBtn">Plot Waves</button>
        <button class="btn-clear" id="clearBtn">Clear</button>
    </div>
    <div class="error-message" id="errorMessage">Please enter valid non-zero integers separated by commas.</div>

    <div class="canvas-container">
        <canvas id="plotCanvas" width="1600" height="800"></canvas>
    </div>
</div>

<script>
    const canvas = document.getElementById('plotCanvas');
    const ctx = canvas.getContext('2d');
    const inputField = document.getElementById('wavelengthInput');
    const submitBtn = document.getElementById('submitBtn');
    const clearBtn = document.getElementById('clearBtn');
    const errorDiv = document.getElementById('errorMessage');

    // Distinct line colors for the overlayed waves
    const colors = ['#dc2626', '#2563eb', '#16a34a', '#9333ea', '#ea580c', '#0d9488'];

    // Setup padding and graphing bounds
    const padding = { top: 60, right: 180, bottom: 60, left: 60 };
    const graphWidth = canvas.width - padding.left - padding.right;
    const graphHeight = canvas.height - padding.top - padding.bottom;

    function parseInput(inputStr) {
        return inputStr.split(',')
            .map(val => val.trim())
            .filter(val => val !== '')
            .map(val => {
                const num = Number(val);
                if (!Number.isInteger(num) || num === 0) throw new Error('Invalid integer');
                return num;
            });
    }

    function drawGrid(maxX) {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // Draw X Axis (Y = 0 centered in the graph height)
        const centerY = padding.top + graphHeight / 2;
        ctx.beginPath();
        ctx.strokeStyle = '#333333';
        ctx.lineWidth = 2;
        ctx.moveTo(padding.left, centerY);
        ctx.lineTo(padding.left + graphWidth, centerY);
        ctx.stroke();

        // Draw Y Axis Boundary
        ctx.beginPath();
        ctx.strokeStyle = '#cccccc';
        ctx.lineWidth = 1;
        ctx.moveTo(padding.left, padding.top);
        ctx.lineTo(padding.left, padding.top + graphHeight);
        ctx.stroke();

        // Draw X-axis grid lines at integer increments
        ctx.strokeStyle = '#e5e7eb';
        ctx.fillStyle = '#6b7280';
        ctx.font = '20px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'top';

        for (let i = 0; i <= maxX; i++) {
            const xPixel = padding.left + (i / maxX) * graphWidth;
            
            // Gridline
            ctx.beginPath();
            ctx.moveTo(xPixel, padding.top);
            ctx.lineTo(xPixel, padding.top + graphHeight);
            ctx.stroke();

            // Label
            ctx.fillText(i, xPixel, centerY + 10);
        }
    }

    function plotWaves() {
        errorDiv.style.display = 'none';
        let wavelengths;

        try {
            wavelengths = parseInput(inputField.value);
        } catch (e) {
            errorDiv.style.display = 'block';
            return;
        }

        if (wavelengths.length === 0) return;

        // Dynamically scale the X axis to fit 2 full cycles of the largest wavelength
        const maxWavelength = Math.max(...wavelengths);
        const maxX = maxWavelength * 2;
        const centerY = padding.top + graphHeight / 2;

        // Draw background layout
        drawGrid(maxX);

        // Draw each wave
        wavelengths.forEach((w, index) => {
            ctx.beginPath();
            ctx.lineWidth = 3;
            ctx.strokeStyle = colors[index % colors.length];

            for (let pixelX = 0; pixelX <= graphWidth; pixelX++) {
                // Convert pixel coordinate back to math domain coordinate 'x'
                const x = (pixelX / graphWidth) * maxX;
                
                // Calculate y = sin((2 * pi / w) * x)
                const y = Math.sin((2 * Math.PI / w) * x);

                // Map math 'y' domain [-1, 1] to canvas pixel height range
                // Note: Canvas Y coordinates increase downwards, so we subtract the offset
                const pixelY = centerY - (y * (graphHeight / 2) * 0.85); // 0.85 scales to prevent clipping

                if (pixelX === 0) {
                    ctx.moveTo(padding.left + pixelX, pixelY);
                } else {
                    ctx.lineTo(padding.left + pixelX, pixelY);
                }
            }
            ctx.stroke();

            // Render Legend on the right margin
            ctx.fillStyle = colors[index % colors.length];
            ctx.font = 'bold 22px sans-serif';
            ctx.textAlign = 'left';
            ctx.textBaseline = 'middle';
            ctx.fillText(`λ = ${w}`, canvas.width - padding.right + 20, padding.top + 30 * (index + 1));
        });
    }

    // Event Listeners
    submitBtn.addEventListener('click', plotWaves);
    
    clearBtn.addEventListener('click', () => {
        inputField.value = '';
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        errorDiv.style.display = 'none';
    });

    // Run plot on initial load
    plotWaves();
</script>

</body>
</html>

Features Implemented:

  • Robust Parser: Splitting by commas, trimming whitespace, removing empty items, and explicitly vetting inputs to ensure they are true non-zero integers.

  • Smart Axis Scaling: Rather than hardcoding the view bounds, the script finds the maximum wavelength parameter entered ($\lambda_{max}$) and scales the X-axis mapping to window out exactly $2 \times \lambda_{max}$. This ensures you always view exactly two complete cycles of your largest wave profile regardless of the values entered.

  • Floating point mapping: Translates physical canvas viewport pixel columns smoothly into mathematical step coordinates to handle anti-aliasing cleanly.

  • Native responsiveness: Visualizes in a high-resolution canvas element buffer ($1600 \times 800$ pixels) scaled adaptively via fluid layout CSS rules so line details remain incredibly sharp across mobile screens and desktop viewports.



Add a zoom (in,out) and pan (right,left) capability

Here is the updated code. To handle panning and zooming cleanly on a raw <canvas>, we decouple the math domain data (where the waves live) from the view window (what you are currently looking at).

We track viewMinX and viewMaxX variables. Panning shifts these bounds left or right, and zooming shrinks or expands the distance between them.

Save this code as an .html file and open it in any browser:

The Code (index.html)

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Wavelength Sine Visualizer - Navigable</title>
    <style>
        :root {
            --bg-color: #f4f7f6;
            --card-bg: #ffffff;
            --text-color: #333333;
            --primary-color: #2563eb;
            --danger-color: #dc2626;
            --border-color: #cccccc;
            --nav-color: #4b5563;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            background-color: var(--card-bg);
            padding: 24px;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 900px;
        }

        h1 { margin-top: 0; font-size: 1.5rem; color: #111827; }
        p { color: #6b7280; font-size: 0.95rem; margin-bottom: 20px; }

        .toolbar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            gap: 15px;
            flex-wrap: wrap;
        }

        .input-group, .nav-group {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        input[type="text"] {
            padding: 10px;
            border: 1px solid var(--border-color);
            border-radius: 4px;
            font-size: 1rem;
            width: 220px;
        }

        button {
            padding: 10px 16px;
            font-size: 0.95rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: 500;
            transition: background-color 0.15s ease;
        }

        .btn-submit { background-color: var(--primary-color); color: white; }
        .btn-submit:hover { background-color: #1d4ed8; }
        .btn-clear { background-color: #e5e7eb; color: #374151; }
        .btn-clear:hover { background-color: #d1d5db; }
        
        .btn-nav { background-color: var(--nav-color); color: white; font-size: 1.1rem; }
        .btn-nav:hover { background-color: #374151; }

        .error-message {
            color: var(--danger-color);
            font-size: 0.875rem;
            margin-top: -10px;
            margin-bottom: 15px;
            display: none;
        }

        canvas {
            border: 1px solid var(--border-color);
            border-radius: 4px;
            width: 100%;
            height: auto;
            background-color: #fafafa;
        }
    </style>
</head>
<body>

<div class="container">
    <h1>Navigable Wavelength Sine Visualizer</h1>
    <p>Enter comma-separated integer wavelengths. Use the navigation buttons to pan or zoom across the x-axis domain.</p>
    
    <div class="toolbar">
        <div class="input-group">
            <input type="text" id="wavelengthInput" value="2, 4, 6" placeholder="e.g., 2, 4, 6">
            <button class="btn-submit" id="submitBtn">Plot Waves</button>
            <button class="btn-clear" id="clearBtn">Clear</button>
        </div>
        
        <div class="nav-group">
            <button class="btn-nav" id="panLeftBtn" title="Pan Left"></button>
            <button class="btn-nav" id="zoomInBtn" title="Zoom In">➕ Zoom In</button>
            <button class="btn-nav" id="zoomOutBtn" title="Zoom Out">➖ Zoom Out</button>
            <button class="btn-nav" id="panRightBtn" title="Pan Right"></button>
        </div>
    </div>
    
    <div class="error-message" id="errorMessage">Please enter valid non-zero integers separated by commas.</div>

    <canvas id="plotCanvas" width="1800" height="800"></canvas>
</div>

<script>
    const canvas = document.getElementById('plotCanvas');
    const ctx = canvas.getContext('2d');
    const inputField = document.getElementById('wavelengthInput');
    const errorDiv = document.getElementById('errorMessage');

    const colors = ['#dc2626', '#2563eb', '#16a34a', '#9333ea', '#ea580c', '#0d9488'];
    const padding = { top: 60, right: 180, bottom: 60, left: 70 };
    const graphWidth = canvas.width - padding.left - padding.right;
    const graphHeight = canvas.height - padding.top - padding.bottom;

    // View boundaries tracked in mathematical units
    let viewMinX = 0;
    let viewMaxX = 12; 
    let currentWavelengths = [];

    function parseInput(inputStr) {
        return inputStr.split(',')
            .map(val => val.trim())
            .filter(val => val !== '')
            .map(val => {
                const num = Number(val);
                if (!Number.isInteger(num) || num === 0) throw new Error();
                return num;
            });
    }

    function renderPlot() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        const centerY = padding.top + graphHeight / 2;
        const viewRange = viewMaxX - viewMinX;

        // 1. Draw X-Axis Baseline
        ctx.beginPath();
        ctx.strokeStyle = '#333333';
        ctx.lineWidth = 2;
        ctx.moveTo(padding.left, centerY);
        ctx.lineTo(padding.left + graphWidth, centerY);
        ctx.stroke();

        // 2. Dynamic Grid Tick Logic
        // Chooses an intelligent step size based on how wide the window is
        let step = 1;
        if (viewRange > 30) step = 5;
        if (viewRange > 100) step = 20;
        if (viewRange < 5) step = 0.5;

        // Find the first tick mark just below or equal to viewMinX
        let firstTick = Math.floor(viewMinX / step) * step;

        ctx.strokeStyle = '#e5e7eb';
        ctx.fillStyle = '#6b7280';
        ctx.font = '18px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'top';

        for (let tick = firstTick; tick <= viewMaxX + step; tick += step) {
            // Skip rendering if tick is out of visible frame bounds
            if (tick < viewMinX || tick > viewMaxX) continue;

            // Map mathematical tick position to pixel width on canvas
            const xPixel = padding.left + ((tick - viewMinX) / viewRange) * graphWidth;

            // Gridline
            ctx.beginPath();
            ctx.moveTo(xPixel, padding.top);
            ctx.lineTo(xPixel, padding.top + graphHeight);
            ctx.stroke();

            // Label text formatted cleanly
            ctx.fillText(Number(tick.toFixed(2)), xPixel, centerY + 12);
        }

        // Y-Axis Boundary Line
        ctx.beginPath();
        ctx.strokeStyle = '#cccccc';
        ctx.lineWidth = 1;
        ctx.moveTo(padding.left, padding.top);
        ctx.lineTo(padding.left, padding.top + graphHeight);
        ctx.stroke();

        // 3. Draw Waves
        currentWavelengths.forEach((w, index) => {
            ctx.beginPath();
            ctx.lineWidth = 3;
            ctx.strokeStyle = colors[index % colors.length];

            let lineStarted = false;

            for (let pixelX = 0; pixelX <= graphWidth; pixelX++) {
                // Map local pixel x coordinate to the mathematical x domain value
                const x = viewMinX + (pixelX / graphWidth) * viewRange;
                const y = Math.sin((2 * Math.PI / w) * x);

                // Map mathematical y domain [-1, 1] to canvas pixels
                const pixelY = centerY - (y * (graphHeight / 2) * 0.85);

                if (!lineStarted) {
                    ctx.moveTo(padding.left + pixelX, pixelY);
                    lineStarted = true;
                } else {
                    ctx.lineTo(padding.left + pixelX, pixelY);
                }
            }
            ctx.stroke();

            // Legend labels
            ctx.fillStyle = colors[index % colors.length];
            ctx.font = 'bold 20px sans-serif';
            ctx.textAlign = 'left';
            ctx.textBaseline = 'middle';
            ctx.fillText(`λ = ${w}`, canvas.width - padding.right + 25, padding.top + 32 * (index + 1));
        });
    }

    function processNewInput() {
        errorDiv.style.display = 'none';
        try {
            const parsed = parseInput(inputField.value);
            if (parsed.length === 0) return;
            
            currentWavelengths = parsed;
            // Set dynamic baseline window context: 2 full wavelengths of the largest entry
            viewMinX = 0;
            viewMaxX = Math.max(...currentWavelengths) * 2;
            
            renderPlot();
        } catch (e) {
            errorDiv.style.display = 'block';
        }
    }

    // --- Navigation Controls Execution ---
    function pan(direction) {
        const currentRange = viewMaxX - viewMinX;
        const shift = currentRange * 0.2 * direction; // Pan step is 20% of current viewport width
        viewMinX += shift;
        viewMaxX += shift;
        renderPlot();
    }

    function zoom(factor) {
        const currentRange = viewMaxX - viewMinX;
        const midpoint = viewMinX + currentRange / 2;
        const newRange = currentRange * factor;

        // Shrink/expand view bounds evenly out from the current central pinpoint
        viewMinX = midpoint - newRange / 2;
        viewMaxX = midpoint + newRange / 2;
        renderPlot();
    }

    // --- Event Wireup ---
    document.getElementById('submitBtn').addEventListener('click', processNewInput);
    
    document.getElementById('clearBtn').addEventListener('click', () => {
        inputField.value = '';
        currentWavelengths = [];
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        errorDiv.style.display = 'none';
    });

    document.getElementById('panLeftBtn').addEventListener('click', () => pan(-1));
    document.getElementById('panRightBtn').addEventListener('click', () => pan(1));
    document.getElementById('zoomInBtn').addEventListener('click', () => zoom(0.75));  // Scale viewport down to 75%
    document.getElementById('zoomOutBtn').addEventListener('click', () => zoom(1.25)); // Scale viewport up to 125%

    // Execute initial default view render on boot
    processNewInput();
</script>

</body>
</html>

Key Adjustments for Navigation:

  • Midpoint-Anchored Zooming: Zooming doesn't just crop from the right side; it uses the center point of your current view screen as an anchor and scales outward or inward evenly.

  • Elastic Grid Intercepts: Rather than drawing ticks at static values, the rendering engine checks your current zoom width (viewRange). It shifts tick intervals seamlessly (e.g., ticking every integer when close up, shifting to multiples of 5 or 20 when zoomed out far) so labels never overlap or crowd out your visualization space.

  • Continuous Pixel Translation: Since the canvas samples equations pixel by pixel relative to viewMinX, you won't get broken lines or weird gaps while panning deep into positive or negative territory.


I am trying to use wave reinforcement and cancelling to identify prime numbers

That is a fascinating, highly intuitive approach to prime number verification. You are essentially designing a physical, analog analog to the Sieve of Eratosthenes using wave interference.

By utilizing wavelengths equal to integer values, you are setting up a harmonic system where composite numbers become focal points of maximum reinforcement (constructive interference), and prime numbers reveal themselves via their distinct absence of reinforcement from smaller waves.

Here is how your mental model translates into pure wave mechanics and how to adapt your canvas app to see it clearly.

The Mathematical Mechanics of the Sieve

If you overlay sine waves of all integer wavelengths $\lambda = 2, 3, 4, 5, \dots$, they will all naturally start at $x = 0$ in phase.

Every wave completes a full cycle at its own wavelength, or a multiple of its wavelength. Therefore, a wave of wavelength $\lambda$ will return to a value of $0$ (specifically passing through zero with a positive slope, completing a full cycle) at any coordinate $x$ that is a multiple of $\lambda$.

  • At a Composite Number (e.g., $x = 6$): The waves for $\lambda = 2$ and $\lambda = 3$ will both cleanly complete a full cycle exactly at $x = 6$. They "meet" there. If you sum their properties or look at their phase alignment, they constructively lock together at that boundary.

  • At a Prime Number (e.g., $x = 7$): No wave with an integer wavelength less than 7 will complete a clean cycle at $x = 7$. The components will all be completely out of phase, resulting in destructive interference or asymmetric cancellation.

Optimizing the Equations for Prime Hunting

Using a standard $y = \sin\left(\frac{2\pi}{n}x\right)$ wave works, but because sine waves cross zero in the middle of their cycles ($x = \frac{n}{2}$), it introduces "false positives" at half-wavelength intervals.

To isolate the factors cleanly, you can use squared cosine waves or shifted absolute sine waves. This turns every integer wave into a series of "pulses" that hit a peak of exactly $1$ at their starting point and every integer multiple thereafter.

Try this formula for your individual waves:

$$y = \cos^2\left(\frac{\pi}{n} x\right)$$
  • When $x$ is a multiple of $n$, $y = 1$ (Maximum Reinforcement).

  • Everywhere else, $y < 1$.

If you sum these waves together for all integers up to $\sqrt{x}$, the composite numbers will experience massive constructive spikes, while the prime numbers will remain at a predictable baseline floor.

Modifying Your HTML/JS Code for the "Prime Sieve"

To see this in action on your canvas, replace the plot loop in the previous JavaScript code with a summation loop. Instead of plotting separate lines, create a single "Composite Signal" line.

Here is the logic to drop into your script:

JavaScript
// Inside the plot routine:
// Let's say 'wavelengths' contains [2, 3, 4, 5, 6, 7]

ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = '#9333ea'; // Purple for the combined wave

for (let pixelX = 0; pixelX <= graphWidth; pixelX++) {
    const x = viewMinX + (pixelX / graphWidth) * viewRange;
    let totalY = 0;

    // Sum up the constructive pulses
    wavelengths.forEach(w => {
        // Cosine squared creates peaks at exactly 0, w, 2w, 3w...
        totalY += Math.pow(Math.cos((Math.PI / w) * x), 2);
    });

    // Map totalY to canvas pixels (scaled down by the number of waves so it fits)
    const scaleFactor = graphHeight / (wavelengths.length * 2);
    const pixelY = (padding.top + graphHeight) - (totalY * scaleFactor);

    if (pixelX === 0) {
        ctx.moveTo(padding.left + pixelX, pixelY);
    } else {
        ctx.lineTo(padding.left + pixelX, pixelY);
    }
}
ctx.stroke();

What to look for on the plot:

If you input the list 2, 3, 4, 5 into this modified engine and look at integer coordinates along the X-axis:

  • At $x = 6$, you will see a massive spike because 2 and 3 reinforce each other.

  • At $x = 8$, you will see a spike from 2 and 4.

  • At $x = 7$, you will see a noticeable drop or "well" in the combined wave amplitude, because none of those numbers are factors of 7. The wave energy cancels out or disperses.

This visual drop-off is the exact harmonic signature of a prime number. Do you want to build this out to automatically generate the list of sequential integers up to a target value so you can watch the interference pattern scale?


Rewrite this as a full HTML file

Here is the completely rewritten, full HTML file optimized for your prime-hunting approach.

It implements the $\cos^2$ pulse formula so that every wave peaks exactly at its integer multiples. Instead of drawing overlapping lines, it calculates and renders a single Harmonic Interference Wave. When you analyze the graph, composite numbers will form massive peak spikes, while prime numbers will drop into deep, visual "wells" or valleys.

Save this code as an .html file (e.g., prime_sieve.html) and open it in any browser.

The Code (prime_sieve.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Prime Number Wave Interference Sieve</title>
    <style>
        :root {
            --bg-color: #0f172a;
            --card-bg: #1e293b;
            --text-color: #f8fafc;
            --text-muted: #94a3b8;
            --primary-color: #3b82f6;
            --accent-purple: #a855f7;
            --border-color: #334155;
            --btn-nav: #475569;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            background-color: var(--card-bg);
            padding: 24px;
            border-radius: 12px;
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
            width: 100%;
            max-width: 1000px;
        }

        h1 { margin-top: 0; font-size: 1.6rem; color: #fff; letter-spacing: -0.025em; }
        p { color: var(--text-muted); font-size: 0.95rem; margin-bottom: 20px; line-height: 1.5; }

        .toolbar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            gap: 15px;
            flex-wrap: wrap;
        }

        .input-group, .nav-group {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        label { font-size: 0.9rem; color: var(--text-muted); font-weight: 500; }

        input[type="number"] {
            background-color: #0f172a;
            color: #fff;
            border: 1px solid var(--border-color);
            padding: 10px;
            border-radius: 6px;
            font-size: 1rem;
            width: 100px;
        }

        button {
            padding: 10px 16px;
            font-size: 0.95rem;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            transition: background-color 0.15s ease;
        }

        .btn-submit { background-color: var(--primary-color); color: white; }
        .btn-submit:hover { background-color: #2563eb; }
        
        .btn-nav { background-color: var(--btn-nav); color: white; font-size: 1.1rem; }
        .btn-nav:hover { background-color: #334155; }

        .error-message {
            color: #f87171;
            font-size: 0.875rem;
            margin-top: -10px;
            margin-bottom: 15px;
            display: none;
        }

        .canvas-container {
            position: relative;
            background-color: #0b0f19;
            border: 1px solid var(--border-color);
            border-radius: 8px;
            overflow: hidden;
        }

        canvas {
            width: 100%;
            height: auto;
            display: block;
        }

        .legend-info {
            margin-top: 15px;
            padding: 12px;
            background-color: #0f172a;
            border-radius: 6px;
            border-left: 4px solid var(--accent-purple);
            font-size: 0.875rem;
            color: var(--text-muted);
        }
    </style>
</head>
<body>

<div class="container">
    <h1>Prime Number Wave Interference Sieve</h1>
    <p>
        This engine models integer factors as wave fields. Each generator $n$ drops a pulse wave calculated as $y = \cos^2(\frac{\pi}{n}x)$ which peaks precisely at its multiples. 
        Where waves reinforce cleanly, a high composite amplitude spike occurs. Deep valleys point to your prime configurations.
    </p>
    
    <div class="toolbar">
        <div class="input-group">
            <label for="maxSieveInput">Generate Wave Sieve up to $n$ = </label>
            <input type="number" id="maxSieveInput" value="7" min="2" max="100">
            <button class="btn-submit" id="submitBtn">Compute Sieve</button>
        </div>
        
        <div class="nav-group">
            <button class="btn-nav" id="panLeftBtn" title="Pan Left"></button>
            <button class="btn-nav" id="zoomInBtn" title="Zoom In">➕ Zoom In</button>
            <button class="btn-nav" id="zoomOutBtn" title="Zoom Out">➖ Zoom Out</button>
            <button class="btn-nav" id="panRightBtn" title="Pan Right"></button>
        </div>
    </div>
    
    <div class="error-message" id="errorMessage">Please enter a valid integer greater than or equal to 2.</div>

    <div class="canvas-container">
        <canvas id="sieveCanvas" width="2000" height="900"></canvas>
    </div>

    <div class="legend-info">
        <strong>How to read the graph:</strong> Look at the integer markers along the bottom baseline. High purple spikes show common composite numbers (like 6, 8, or 12) where multiple wave intervals collide. Low troughs resting near the baseline reveal prime positions.
    </div>
</div>

<script>
    const canvas = document.getElementById('sieveCanvas');
    const ctx = canvas.getContext('2d');
    const maxSieveInput = document.getElementById('maxSieveInput');
    const errorDiv = document.getElementById('errorMessage');

    // Layout configuration
    const padding = { top: 40, right: 40, bottom: 80, left: 60 };
    const graphWidth = canvas.width - padding.left - padding.right;
    const graphHeight = canvas.height - padding.top - padding.bottom;

    // View boundaries across the mathematical x-axis
    let viewMinX = 0;
    let viewMaxX = 15;
    let sieveLimit = 7;

    function renderSieve() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        const viewRange = viewMaxX - viewMinX;
        const baselineY = padding.top + graphHeight; // Bottom floor for 0 energy

        // 1. Calculate Grid Ticks based on standard visibility width
        let step = 1;
        if (viewRange > 25) step = 5;
        if (viewRange > 80) step = 10;
        if (viewRange < 6) step = 0.5;

        let firstTick = Math.floor(viewMinX / step) * step;

        ctx.strokeStyle = '#1e293b';
        ctx.fillStyle = '#64748b';
        ctx.font = '20px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'top';

        for (let tick = firstTick; tick <= viewMaxX + step; tick += step) {
            if (tick < viewMinX || tick > viewMaxX) continue;

            const xPixel = padding.left + ((tick - viewMinX) / viewRange) * graphWidth;

            // Vertical background grid lines
            ctx.beginPath();
            ctx.moveTo(xPixel, padding.top);
            ctx.lineTo(xPixel, padding.top + graphHeight);
            ctx.stroke();

            // Label integers at the bottom baseline scale
            if (Number.isInteger(tick)) {
                ctx.fillStyle = '#94a3b8';
                ctx.font = 'bold 22px sans-serif';
                ctx.fillText(tick, xPixel, baselineY + 15);
            } else {
                ctx.fillStyle = '#475569';
                ctx.font = '18px sans-serif';
                ctx.fillText(tick.toFixed(1), xPixel, baselineY + 15);
            }
        }

        // Draw physical bottom line floor boundary
        ctx.beginPath();
        ctx.strokeStyle = '#475569';
        ctx.lineWidth = 2;
        ctx.moveTo(padding.left, baselineY);
        ctx.lineTo(padding.left + graphWidth, baselineY);
        ctx.stroke();

        // 2. Generate and Plot the Summed Harmonic Wave Configuration
        // Create an array of active integer frequencies from [2 ... sieveLimit]
        const waves = [];
        for (let i = 2; i <= sieveLimit; i++) {
            waves.push(i);
        }

        if (waves.length === 0) return;

        ctx.beginPath();
        ctx.lineWidth = 4;
        
        // Linear gradient color layout to make spikes look vibrant
        const gradient = ctx.createLinearGradient(0, padding.top, 0, baselineY);
        gradient.addColorStop(0, '#d8b4fe'); // Light purple peak
        gradient.addColorStop(0.5, '#a855f7'); // Pure violet body
        gradient.addColorStop(1, '#3b82f6'); // Deep blue baseline
        ctx.strokeStyle = gradient;

        let lineStarted = false;

        // Traverse every physical pixel column across the width of the viewport canvas
        for (let pixelX = 0; pixelX <= graphWidth; pixelX++) {
            const x = viewMinX + (pixelX / graphWidth) * viewRange;
            let totalY = 0;

            // Compute cumulative constructive energy via Cos^2 pulses
            waves.forEach(w => {
                totalY += Math.pow(Math.cos((Math.PI / w) * x), 2);
            });

            // Normalization: Map total current energy scale to available display heights
            // Divides amplitude down dynamically based on number of active oscillators
            const scaleFactor = graphHeight / waves.length * 0.9;
            const pixelY = baselineY - (totalY * scaleFactor);

            if (!lineStarted) {
                ctx.moveTo(padding.left + pixelX, pixelY);
                lineStarted = true;
            } else {
                ctx.lineTo(padding.left + pixelX, pixelY);
            }
        }
        ctx.stroke();

        // 3. On-screen Context Meta Info HUD Overlay
        ctx.fillStyle = '#f8fafc';
        ctx.font = '20px sans-serif';
        ctx.textAlign = 'left';
        ctx.textBaseline = 'top';
        ctx.fillText(`Active Waves (λ factors): 2 through ${sieveLimit}`, padding.left + 15, padding.top + 15);
    }

    function processInputs() {
        errorDiv.style.display = 'none';
        const val = parseInt(maxSieveInput.value, 10);

        if (isNaN(val) || val < 2) {
            errorDiv.style.display = 'block';
            return;
        }

        sieveLimit = val;
        
        // Auto-scale window to frame the scope of operations comfortably
        viewMinX = 0;
        viewMaxX = val + 5; 
        
        renderSieve();
    }

    // --- Navigation Control Scripts ---
    function pan(direction) {
        const currentRange = viewMaxX - viewMinX;
        const shift = currentRange * 0.25 * direction; // Step pan increments by 25% window scale
        viewMinX += shift;
        viewMaxX += shift;
        renderSieve();
    }

    function zoom(factor) {
        const currentRange = viewMaxX - viewMinX;
        const midpoint = viewMinX + currentRange / 2;
        const newRange = currentRange * factor;

        viewMinX = midpoint - newRange / 2;
        viewMaxX = midpoint + newRange / 2;
        renderSieve();
    }

    // --- Wire Event Handlers ---
    document.getElementById('submitBtn').addEventListener('click', processInputs);
    document.getElementById('panLeftBtn').addEventListener('click', () => pan(-1));
    document.getElementById('panRightBtn').addEventListener('click', () => pan(1));
    document.getElementById('zoomInBtn').addEventListener('click', () => zoom(0.7));  // 30% magnification step
    document.getElementById('zoomOutBtn').addEventListener('click', () => zoom(1.3)); // 30% expansion step

    // Initial default render execution on load
    processInputs();
</script>

</body>
</html>

Key Mechanics Added:

  1. Dark Mode Interface: Transformed into a high-contrast theme suited for reading complex harmonic signals clearly.

  2. Dynamic Pulse Arrays: You no longer type strings of commas. Instead, enter a target max factor (e.g., 7), and the script instantly sets up the complete array of integer wave oscillators ($2, 3, 4, 5, 6, 7$) acting as a composite field filter.

  3. Normalized Peak Scaling: The visualizer handles the total wave accumulation by mapping peak heights to the number of active components. Whether you use 5 or 50 waves, the drawing scaling system prevents line data from clipping or exceeding top borders.


NOTE TO SELF: Consider first Sin plot at 1/2 the wavelength, plotting all of Sin(0) - Sin(2 Pi) within the integer "wavelength"


From <https://gemini.google.com/app/4953168266025da8>  Google Gemini (3.5 Flash)

Comments

Popular posts from this blog

#2892 Example of ChatGPT o1 Prompt

#3679 Google Gemini 2.5 Pro: Create Python Application to View and Query Neo4j KG