Skip to content

Commit 5b4f26a

Browse files
committed
Add Jacobian demo (not directly FDSP related), but useful in 1-1 RV transforms
1 parent 7f9ecac commit 5b4f26a

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

redirects/jacobian.html

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Jacobian Visualizer</title>
7+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
8+
<style>
9+
.container {
10+
display: flex;
11+
}
12+
.plot {
13+
width: 45%;
14+
margin: 2%;
15+
}
16+
.input-group {
17+
margin-bottom: 10px;
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
23+
<H1> Jacobian Visualizer </H1>
24+
25+
<p>Input two functions (in Javascript syntax). Specify an input region in terms of an x and y coordinate and x and y deltas. The input and output regions will be shown. Start with linear transforms. What do you observe. Now consider some nonlinear transforms -- note that for nonlinear transforms, the ratio of the areas can depend on the (x,y) coordinates. Set the delta values to something very small. What do you observe about the output areas. Compare the ratio of the input and output areas to the Jacobian when the delta values are small. </p>
26+
27+
<div>
28+
<div class="input-group">
29+
<label for="g">Function g(x, y): </label>
30+
<input type="text" id="g" value="x + y">
31+
</div>
32+
<div class="input-group">
33+
<label for="h">Function h(x, y): </label>
34+
<input type="text" id="h" value="x - y">
35+
</div>
36+
<div class="input-group">
37+
<label for="x">Location x: </label>
38+
<input type="number" id="x" value="0">
39+
</div>
40+
<div class="input-group">
41+
<label for="y">Location y: </label>
42+
<input type="number" id="y" value="0">
43+
</div>
44+
<div class="input-group">
45+
<label for="deltaX">Offset deltaX: </label>
46+
<input type="number" id="deltaX" value="1">
47+
</div>
48+
<div class="input-group">
49+
<label for="deltaY">Offset deltaY: </label>
50+
<input type="number" id="deltaY" value="1">
51+
</div>
52+
</div>
53+
54+
<div class="container">
55+
<div class="plot" id="original"></div>
56+
<div class="plot" id="transformed"></div>
57+
</div>
58+
59+
<div>
60+
<p>Input area: <span id="input-area"></span></p>
61+
<p>Estimate of output area: <span id="output-area"></span></p>
62+
<p></p>
63+
<p>Output area/Input area: <span id="area-ratio"></span></p>
64+
</div>
65+
66+
<script>
67+
document.querySelectorAll('input').forEach(input => {
68+
input.addEventListener('input', updatePlots);
69+
});
70+
71+
function evaluateFunctions(g, h, x, y) {
72+
try {
73+
const gx = new Function('x', 'y', `return ${g}`);
74+
const hy = new Function('x', 'y', `return ${h}`);
75+
return [gx(x, y), hy(x, y)];
76+
} catch (error) {
77+
console.error('Error evaluating functions:', error);
78+
return [NaN, NaN];
79+
}
80+
}
81+
82+
function sampleEdge(x1, y1, x2, y2, numPoints) {
83+
const points = [];
84+
for (let i = 0; i <= numPoints; i++) {
85+
const t = i / numPoints;
86+
points.push([x1 + t * (x2 - x1), y1 + t * (y2 - y1)]);
87+
}
88+
return points;
89+
}
90+
91+
function polygonArea(vertices) {
92+
const n = vertices.length;
93+
if (n < 3) return 0; // A polygon must have at least 3 vertices
94+
95+
let area = 0;
96+
97+
// Calculate the area using the Shoelace formula
98+
for (let i = 0; i < n; i++) {
99+
let j = (i + 1) % n; // Wrap around to the first vertex for the last edge
100+
area += vertices[i][0] * vertices[j][1];
101+
area -= vertices[j][0] * vertices[i][1];
102+
}
103+
104+
return Math.abs(area) / 2;
105+
}
106+
107+
function polygonAreaEdges(edges) {
108+
var vertices = [];
109+
for (i=0; i < edges.length; i++) {
110+
vertices = vertices.concat(edges[i]);
111+
}
112+
113+
const n = vertices.length;
114+
if (n < 3) return 0; // A polygon must have at least 3 vertices
115+
116+
let area = 0;
117+
118+
// Calculate the area using the Shoelace formula
119+
for (let i = 0; i < n; i++) {
120+
let j = (i + 1) % n; // Wrap around to the first vertex for the last edge
121+
area += vertices[i][0] * vertices[j][1];
122+
area -= vertices[j][0] * vertices[i][1];
123+
}
124+
125+
return Math.abs(area) / 2;
126+
}
127+
128+
129+
function updatePlots() {
130+
const g = document.getElementById('g').value;
131+
const h = document.getElementById('h').value;
132+
const x = parseFloat(document.getElementById('x').value);
133+
const y = parseFloat(document.getElementById('y').value);
134+
const deltaX = parseFloat(document.getElementById('deltaX').value);
135+
const deltaY = parseFloat(document.getElementById('deltaY').value);
136+
const numPoints = 100; // Number of sample points per edge
137+
138+
// Define the rectangle vertices
139+
const rectEdges = [
140+
sampleEdge(x, y, x + deltaX, y, numPoints),
141+
sampleEdge(x + deltaX, y, x + deltaX, y + deltaY, numPoints),
142+
sampleEdge(x + deltaX, y + deltaY, x, y + deltaY, numPoints),
143+
sampleEdge(x, y + deltaY, x, y, numPoints)
144+
];
145+
146+
// Print the areas
147+
const inputArea = polygonArea([ [x,y], [x,y + deltaY], [x+deltaX, y+deltaY], [x+deltaX, y]]);
148+
document.getElementById("input-area").textContent = inputArea.toPrecision(3);
149+
150+
// Flatten the array of edges
151+
const rectPoints = rectEdges.flat();
152+
153+
// Apply transformation
154+
const transformedPoints = rectPoints.map(([xi, yi]) => evaluateFunctions(g, h, xi, yi));
155+
156+
const outputArea = polygonArea(transformedPoints);
157+
document.getElementById("output-area").textContent = outputArea.toPrecision(3);
158+
159+
document.getElementById("area-ratio").textContent = (outputArea/inputArea).toPrecision(3);
160+
161+
// Define the edges colors
162+
const colors = ['red', 'green', 'blue', 'orange'];
163+
164+
// Plot the original rectangle
165+
const originalPlot = {
166+
data: rectEdges.map((edge, i) => ({
167+
x: edge.map(p => p[0]),
168+
y: edge.map(p => p[1]),
169+
type: 'scatter',
170+
mode: 'lines+markers',
171+
marker: { color: colors[i] },
172+
line: { color: colors[i] }
173+
})),
174+
layout: {
175+
title: 'Original Rectangle',
176+
xaxis: { title: 'x' },
177+
yaxis: { title: 'y', scaleanchor: 'x' },
178+
hovermode: 'closest'
179+
}
180+
};
181+
182+
// Plot the transformed shape
183+
const transformedPlot = {
184+
data: rectEdges.map((edge, i) => {
185+
const transformedEdge = edge.map(([xi, yi]) => evaluateFunctions(g, h, xi, yi));
186+
return {
187+
x: transformedEdge.map(p => p[0]),
188+
y: transformedEdge.map(p => p[1]),
189+
type: 'scatter',
190+
mode: 'lines+markers',
191+
marker: { color: colors[i] },
192+
line: { color: colors[i] }
193+
}
194+
}),
195+
layout: {
196+
title: 'Transformed Shape',
197+
xaxis: { title: 'g(x, y)' },
198+
yaxis: { title: 'h(x, y)', scaleanchor: 'x' },
199+
hovermode: 'closest'
200+
}
201+
};
202+
203+
Plotly.newPlot('original', originalPlot.data, originalPlot.layout);
204+
Plotly.newPlot('transformed', transformedPlot.data, transformedPlot.layout);
205+
206+
// Add hover event listeners to link the plots
207+
const originalPlotElement = document.getElementById('original');
208+
const transformedPlotElement = document.getElementById('transformed');
209+
210+
originalPlotElement.on('plotly_hover', function(data) {
211+
const pointIndex = data.points[0].pointIndex;
212+
Plotly.Fx.hover('transformed', [
213+
{ curveNumber: data.points[0].curveNumber, pointIndex: pointIndex }
214+
]);
215+
});
216+
217+
transformedPlotElement.on('plotly_hover', function(data) {
218+
const pointIndex = data.points[0].pointIndex;
219+
Plotly.Fx.hover('original', [
220+
{ curveNumber: data.points[0].curveNumber, pointIndex: pointIndex }
221+
]);
222+
});
223+
224+
originalPlotElement.on('plotly_unhover', function(data) {
225+
Plotly.Fx.unhover('transformed');
226+
});
227+
228+
transformedPlotElement.on('plotly_unhover', function(data) {
229+
Plotly.Fx.unhover('original');
230+
});
231+
}
232+
233+
updatePlots(); // Initial plot
234+
</script>
235+
236+
</body>
237+
</html>

0 commit comments

Comments
 (0)