0% found this document useful (0 votes)
3 views11 pages

Exsolvent Field — Rational Function Visualizer.html

The document outlines the structure of an interactive web application called the Exsolvent Field Visualizer, which allows users to explore rational function fields for multi-scale porous media through adjustable parameters like porosity and permeability. It features various plots, including a 3D topography of effective permeability and a percolation lattice, and utilizes Plotly for rendering. The application aims to provide insights into the relationships between different parameters affecting fluid flow in porous media.

Uploaded by

adipedagogy
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views11 pages

Exsolvent Field — Rational Function Visualizer.html

The document outlines the structure of an interactive web application called the Exsolvent Field Visualizer, which allows users to explore rational function fields for multi-scale porous media through adjustable parameters like porosity and permeability. It features various plots, including a 3D topography of effective permeability and a percolation lattice, and utilizes Plotly for rendering. The application aims to provide insights into the relationships between different parameters affecting fluid flow in porous media.

Uploaded by

adipedagogy
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

<!

DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Exsolvent Field — Rational Function Visualizer</title>
<!-- Plotly CDN for interactive 3D and 2D plots -->
<script src="[Link]
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0b0e14;
color: #e0e4ea;
margin: 0;
padding: 20px;
}
.app-container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
text-align: center;
font-weight: 300;
letter-spacing: 2px;
border-bottom: 1px solid #2a3a4a;
padding-bottom: 15px;
color: #b8d0e0;
}
h1 span {
color: #6a9bbf;
font-weight: 500;
}
.subtitle {
text-align: center;
color: #8899aa;
font-style: italic;
margin-top: -12px;
margin-bottom: 30px;
font-size: 0.95rem;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 30px 50px;
background: #141a22;
padding: 20px 30px;
border-radius: 12px;
margin-bottom: 30px;
border: 1px solid #2a3a4a;
justify-content: center;
}
.control-group {
display: flex;
flex-direction: column;
min-width: 160px;
}
.control-group label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 1px;
color: #7a8fa0;
margin-bottom: 4px;
}
.control-group .value {
font-size: 1rem;
font-weight: 600;
color: #b0d0e8;
}
input[type="range"] {
width: 100%;
min-width: 120px;
background: #1e2a34;
height: 4px;
border-radius: 4px;
-webkit-appearance: none;
appearance: none;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #6a9bbf;
cursor: pointer;
border: none;
transition: 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.15);
background: #8ab8df;
}
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 25px;
}
.plot-card {
background: #141a22;
border-radius: 12px;
padding: 15px;
border: 1px solid #1e2e3e;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.plot-card h3 {
margin: 0 0 10px 0;
font-weight: 400;
font-size: 1rem;
color: #9ab0c8;
letter-spacing: 0.5px;
}
.plot-card .sub {
font-size: 0.75rem;
color: #6a7f90;
margin-top: -6px;
margin-bottom: 12px;
}
.plot-card .plot-container {
width: 100%;
height: 400px;
}
.full-width {
grid-column: 1 / -1;
}
.footer-note {
text-align: center;
font-size: 0.8rem;
color: #4a5f70;
margin-top: 30px;
border-top: 1px solid #1a2a3a;
padding-top: 20px;
}
.footer-note a {
color: #6a9bbf;
text-decoration: none;
}
@media (max-width: 900px) {
.grid-container {
grid-template-columns: 1fr;
}
.controls {
gap: 15px;
padding: 15px;
}
}
</style>
</head>
<body>
<div class="app-container">
<h1> <span>Exsolvent</span> Field Visualizer</h1>
<div class="subtitle">Rational Function Field for Multi-Scale Porous Media —
Weighted Polynomial Upscaling</div>

<!-- Controls -->


<div class="controls">
<div class="control-group">
<label>Porosity φ <span class="value" id="phiVal">0.45</span></label>
<input type="range" id="phiSlider" min="0.05" max="0.95" step="0.01"
value="0.45" />
</div>
<div class="control-group">
<label>Matrix Permeability k <span class="value"
id="kVal">0.50</span></label>
<input type="range" id="kSlider" min="0.01" max="1.0" step="0.01"
value="0.50" />
</div>
<div class="control-group">
<label>Percolation Threshold φ<sub>c</sub> <span class="value"
id="phiCVal">0.40</span></label>
<input type="range" id="phiCSlider" min="0.10" max="0.70" step="0.01"
value="0.40" />
</div>
<div class="control-group">
<label>Polynomial Weight α <span class="value"
id="alphaVal">0.50</span></label>
<input type="range" id="alphaSlider" min="0.00" max="1.00" step="0.01"
value="0.50" />
</div>
<div class="control-group">
<label>Grid Resolution <span class="value"
id="resVal">60</span></label>
<input type="range" id="resSlider" min="30" max="120" step="5"
value="60" />
</div>
</div>

<!-- Plots Grid -->


<div class="grid-container">
<div class="plot-card">
<h3> 🌊 3D Topography — Effective Permeability K<sub>eff</sub></h3>
<div class="sub">Surface shows the rational field R(φ, k) = P(φ, k) /
Q(φ, k). Poles → percolation thresholds.</div>
<div id="plot3d" class="plot-container"></div>
</div>
<div class="plot-card">
<h3> 🧮 Weighted Polynomial P(φ, k)</h3>
<div class="sub">Idealized flow (series × parallel) — the Watcher's
projection before encountering the abyss.</div>
<div id="plotContour" class="plot-container"></div>
</div>
<div class="plot-card full-width">
<h3> 🪨 Physical Ground — Percolation Lattice</h3>
<div class="sub">White = conductive pore space (water flows). Black =
impermeable barrier. Red outline = largest connected cluster (Active Anchor
path).</div>
<div id="plotLattice" class="plot-container"
style="height:420px;"></div>
</div>
</div>

<div class="footer-note">
⚡ The Watcher inhabits the gap between the abstract field (top) and the
physical ground (bottom).<br/>
Drag sliders to explore the structural tension. <span
style="color:#4a6a7a;">— Exsolvent Geometry v1.0</span>
</div>
</div>

<script>
(function() {
"use strict";

// --- DOM refs ---


const phiSlider = [Link]('phiSlider');
const kSlider = [Link]('kSlider');
const phiCSlider = [Link]('phiCSlider');
const alphaSlider = [Link]('alphaSlider');
const resSlider = [Link]('resSlider');
const phiVal = [Link]('phiVal');
const kVal = [Link]('kVal');
const phiCVal = [Link]('phiCVal');
const alphaVal = [Link]('alphaVal');
const resVal = [Link]('resVal');

// --- Core math ---


function rationalField(phi, k, phiC, alpha) {
// Numerator: weighted polynomial (parallel + series mixing)
const P = alpha * (phi * phi) * k + (1 - alpha) * 0.5 * phi * (1 - phi)
* [Link](k);
// Denominator: percolation threshold pole
const Q = (phi - phiC) * (phi - phiC) + 0.005;
return P / Q;
}

function computeField(n, phiC, alpha, kFixed) {


const phiVals = new Float64Array(n);
const kVals = new Float64Array(n);
const K_eff = new Float64Array(n * n);
const P_vals = new Float64Array(n * n);

for (let i = 0; i < n; i++) {


phiVals[i] = 0.01 + (0.98 * i) / (n - 1);
kVals[i] = 0.01 + (0.99 * i) / (n - 1);
}

for (let j = 0; j < n; j++) {


for (let i = 0; i < n; i++) {
const phi = phiVals[i];
const k = kVals[j];
const idx = j * n + i;
const P = alpha * (phi * phi) * k + (1 - alpha) * 0.5 * phi *
(1 - phi) * [Link](k);
P_vals[idx] = P;
const Q = (phi - phiC) * (phi - phiC) + 0.005;
K_eff[idx] = P / Q;
}
}

return { phiVals, kVals, K_eff, P_vals };


}

// --- Generate lattice (percolation) ---


function generateLattice(size, p, seed) {
// Simple pseudo-random generator with seed
let s = seed || 42;
function rand() {
s = (s * 9301 + 49297) % 233280;
return s / 233280;
}
const grid = new Uint8Array(size * size);
for (let i = 0; i < size * size; i++) {
grid[i] = rand() < p ? 1 : 0;
}
return grid;
}

// --- Find largest connected cluster (4-connectivity) ---


function largestCluster(grid, size) {
const visited = new Uint8Array(size * size);
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
let maxSize = 0;
let bestLabel = -1;
let labelMap = new Int32Array(size * size);
let currentLabel = 0;

for (let y = 0; y < size; y++) {


for (let x = 0; x < size; x++) {
const idx = y * size + x;
if (grid[idx] === 1 && !visited[idx]) {
// BFS
const queue = [[x,y]];
visited[idx] = 1;
let count = 0;
const clusterIndices = [];
while ([Link] > 0) {
const [cx, cy] = [Link]();
const cidx = cy * size + cx;
[Link](cidx);
count++;
for (const d of dirs) {
const nx = cx + d[0];
const ny = cy + d[1];
if (nx >= 0 && nx < size && ny >= 0 && ny < size) {
const nidx = ny * size + nx;
if (grid[nidx] === 1 && !visited[nidx]) {
visited[nidx] = 1;
[Link]([nx, ny]);
}
}
}
}
if (count > maxSize) {
maxSize = count;
bestLabel = currentLabel;
}
// Assign label to this cluster
for (const ci of clusterIndices) {
labelMap[ci] = currentLabel;
}
currentLabel++;
}
}
}
// Build mask for largest cluster
const mask = new Uint8Array(size * size);
if (bestLabel >= 0) {
for (let i = 0; i < size * size; i++) {
if (labelMap[i] === bestLabel) mask[i] = 1;
}
}
return mask;
}

// --- Plotly rendering ---


function renderAll() {
const phi = parseFloat([Link]);
const k = parseFloat([Link]);
const phiC = parseFloat([Link]);
const alpha = parseFloat([Link]);
const res = parseInt([Link], 10);

[Link] = [Link](2);
[Link] = [Link](2);
[Link] = [Link](2);
[Link] = [Link](2);
[Link] = res;

const n = res;
const data = computeField(n, phiC, alpha, k);

// --- 3D Surface ---


const trace3d = {
type: 'surface',
x: [Link],
y: [Link],
z: [Link]({ length: n }, (_, j) =>
[Link]({ length: n }, (_, i) => data.K_eff[j * n + i])
),
colorscale: 'Plasma',
showscale: true,
colorbar: { title: 'K<sub>eff</sub>', len: 0.6 },
hovertemplate: 'φ: %{x:.2f}<br>k: %{y:.2f}<br>K_eff: %
{z:.2f}<extra></extra>'
};

// Mark the current (phi, k) point on the surface


let zAtPoint = 0;
try {
zAtPoint = rationalField(phi, k, phiC, alpha);
} catch(e) { zAtPoint = 0; }
const pointTrace = {
type: 'scatter3d',
mode: 'markers',
x: [phi],
y: [k],
z: [zAtPoint],
marker: { color: 'red', size: 8, symbol: 'diamond' },
name: 'Watcher Position'
};

const layout3d = {
margin: { l: 30, r: 30, b: 40, t: 20 },
scene: {
xaxis: { title: 'Porosity φ', color: '#8899aa' },
yaxis: { title: 'Permeability k', color: '#8899aa' },
zaxis: { title: 'K<sub>eff</sub>', color: '#8899aa' },
bgcolor: '#0b0e14',
camera: { eye: { x: 1.8, y: 1.8, z: 1.2 } }
},
paper_bgcolor: '#141a22',
plot_bgcolor: '#0b0e14',
font: { color: '#c0d0e0' }
};

[Link]('plot3d', [trace3d, pointTrace], layout3d, { responsive:


true });

// --- Contour: Weighted Polynomial P(φ,k) ---


const pMatrix = [Link]({ length: n }, (_, j) =>
[Link]({ length: n }, (_, i) => data.P_vals[j * n + i])
);

const traceContour = {
type: 'contour',
x: [Link],
y: [Link],
z: pMatrix,
colorscale: 'Viridis',
showscale: true,
colorbar: { title: 'P(φ,k)', len: 0.6 },
contours: { coloring: 'heatmap' },
hovertemplate: 'φ: %{x:.2f}<br>k: %{y:.2f}<br>P:
%{z:.2f}<extra></extra>'
};

const layoutContour = {
margin: { l: 40, r: 30, b: 40, t: 20 },
xaxis: { title: 'Porosity φ', color: '#8899aa', gridcolor:
'#1a2a3a' },
yaxis: { title: 'Matrix Permeability k', color: '#8899aa',
gridcolor: '#1a2a3a' },
paper_bgcolor: '#141a22',
plot_bgcolor: '#0b0e14',
font: { color: '#c0d0e0' }
};

[Link]('plotContour', [traceContour], layoutContour,


{ responsive: true });

// --- Lattice (Percolation) ---


const size = 80;
const pLattice = [Link](0.9, [Link](0.2, 0.4 + (phi - 0.3) * 0.8));
const grid = generateLattice(size, pLattice, 42 + [Link](phi *
1000));
const clusterMask = largestCluster(grid, size);
// Build RGB matrix for the lattice
const rgbData = [];
for (let y = 0; y < size; y++) {
const row = [];
for (let x = 0; x < size; x++) {
const idx = y * size + x;
if (grid[idx] === 0) {
[Link]([10, 15, 25]); // dark impermeable
} else {
[Link]([200, 220, 240]); // white-ish pore
}
}
[Link](row);
}

// Overlay cluster outline in red using a custom trace (scatter)


const clusterPoints = [];
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const idx = y * size + x;
if (clusterMask[idx] === 1) {
// Check if it's on the boundary of the cluster
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
let isBoundary = false;
for (const d of dirs) {
const nx = x + d[0];
const ny = y + d[1];
if (nx < 0 || nx >= size || ny < 0 || ny >= size) {
isBoundary = true;
break;
}
const nidx = ny * size + nx;
if (clusterMask[nidx] === 0) {
isBoundary = true;
break;
}
}
if (isBoundary) {
[Link]({ x: x, y: y });
}
}
}
}

// Build heatmap for the lattice


const zLattice = [Link]({ length: size }, (_, j) =>
[Link]({ length: size }, (_, i) => grid[j * size + i] ? 1 : 0)
);

const traceLattice = {
type: 'heatmap',
z: zLattice,
colorscale: [[0, '#0a0f18'], [1, '#d0e4f0']],
showscale: false,
hovertemplate: 'x: %{x}<br>y: %{y}<br>Pore: %{z}<extra></extra>'
};

// Scatter for the cluster boundary


const traceCluster = {
type: 'scatter',
mode: 'markers',
x: [Link](p => p.x),
y: [Link](p => p.y),
marker: { color: '#ff4466', size: 1.5, opacity: 0.8 },
name: 'Active Anchor Path'
};

const layoutLattice = {
margin: { l: 10, r: 10, b: 10, t: 20 },
xaxis: { showgrid: false, showticklabels: false, range: [-1,
size] },
yaxis: { showgrid: false, showticklabels: false, range: [-1, size],
scaleanchor: 'x' },
paper_bgcolor: '#141a22',
plot_bgcolor: '#0b0e14',
font: { color: '#c0d0e0' },
annotations: [{
x: 0.5,
y: 1.05,
xref: 'paper',
yref: 'paper',
text: `φ = ${[Link](2)} | p(pore) = $
{[Link](2)} | Largest cluster: ${[Link]} cells`,
showarrow: false,
font: { size: 11, color: '#8aaabc' }
}]
};

const latticeTraces = [traceLattice];


if ([Link] > 0) {
[Link](traceCluster);
}

[Link]('plotLattice', latticeTraces, layoutLattice, { responsive:


true });
}

// --- Event binding ---


const sliders = [phiSlider, kSlider, phiCSlider, alphaSlider, resSlider];
[Link](s => [Link]('input', renderAll));

// --- Initial render ---


renderAll();

// --- Window resize ---


[Link]('resize', () => {
// Plotly auto-resizes with responsive: true
});

})();
</script>

</body>
</html>

You might also like