#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)
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
Install the requirements (if you haven't already):
Bashpip install streamlit matplotlib numpySave the code above into a file named
app.py.Run the app from your terminal:
Bashstreamlit 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$.
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:
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)
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)
<!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.
<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)
<!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.
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:
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:
// 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:
Dark Mode Interface: Transformed into a high-contrast theme suited for reading complex harmonic signals clearly.
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.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
Post a Comment