@@ -78,13 +78,15 @@ import {
7878import {
7979 CONTINUOUS_BOUNDARY_FLOW_SETTINGS ,
8080 DEFAULT_GEOMETRIC_OFFSET_SETTINGS ,
81+ DEFAULT_INVARIANT_SET_SETTINGS ,
8182 DEFAULT_MANIFOLD_SETTINGS ,
8283 DEFAULT_ULAM_SETTINGS ,
8384 INVERSE_OFFSET_POSITION_TOLERANCE_RULE ,
8485 ULAM_OPERATOR_SETTINGS ,
8586 continuousUlamIntegrationTime ,
8687 inverseOffsetPositionTolerance
8788} from './config/numericalSettings' ;
89+ import { randomHenonInvariantSeed } from './utils/invariantSetSeed' ;
8890import {
8991 buildExperimentBundle ,
9092 experimentConfigurationToUiState ,
@@ -105,6 +107,7 @@ import type {
105107 ExtendedPointTuple ,
106108 ExtendedState ,
107109 GeometricOffsetState ,
110+ InvariantSetState ,
108111 Manifold ,
109112 ManifoldBranch ,
110113 ManifoldState ,
@@ -510,6 +513,16 @@ const SetValuedViz = () => {
510513 } ;
511514 } ) ;
512515
516+ const [ invariantSetState , setInvariantSetState ] = useState < InvariantSetState > ( {
517+ seedMode : 'manual' ,
518+ boundaryPointCount : DEFAULT_INVARIANT_SET_SETTINGS . boundaryPointCount ,
519+ forwardIterations : DEFAULT_INVARIANT_SET_SETTINGS . forwardIterations ,
520+ showResult : true ,
521+ isComputing : false ,
522+ result : null ,
523+ error : null ,
524+ } ) ;
525+
513526 const boundarySourceManifolds = manifoldState . manifolds ;
514527
515528 const verifiedBoundaryCycles = useMemo (
@@ -697,6 +710,7 @@ const SetValuedViz = () => {
697710 const periodicComputationRevisionRef = useRef ( 0 ) ;
698711 const geometricOffsetRequestIdRef = useRef ( 0 ) ;
699712 const inverseOffsetRequestIdRef = useRef ( 0 ) ;
713+ const invariantSetRequestIdRef = useRef ( 0 ) ;
700714 const ulamDebounceRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
701715 const ulamSupportRef = useRef < SupportGrid | null > ( null ) ;
702716 const ulamTransitionsRequestRef = useRef ( 0 ) ;
@@ -2189,6 +2203,83 @@ const SetValuedViz = () => {
21892203 }
21902204 } , [ recordingState . recordingEnabled ] ) ;
21912205
2206+ const computeForwardInvariantSet = useCallback ( async ( ) => {
2207+ if ( dynamicSystem !== 'henon' ) return ;
2208+ let seed : ExtendedState ;
2209+ try {
2210+ if ( invariantSetState . seedMode === 'manual' ) {
2211+ seed = { ...manifoldState . startPoint } ;
2212+ } else {
2213+ const position = randomHenonInvariantSeed (
2214+ viewRange ,
2215+ params . a ,
2216+ params . b ,
2217+ params . epsilon ,
2218+ ) ;
2219+ seed = {
2220+ ...position ,
2221+ nx : manifoldState . startPoint . nx ,
2222+ ny : manifoldState . startPoint . ny ,
2223+ } ;
2224+ }
2225+ } catch ( error ) {
2226+ setInvariantSetState ( previous => ( {
2227+ ...previous ,
2228+ error : error instanceof Error ? error . message : String ( error ) ,
2229+ } ) ) ;
2230+ return ;
2231+ }
2232+
2233+ const requestId = ++ invariantSetRequestIdRef . current ;
2234+ setInvariantSetState ( previous => ( {
2235+ ...previous ,
2236+ isComputing : true ,
2237+ result : null ,
2238+ error : null ,
2239+ } ) ) ;
2240+ try {
2241+ const result = await runComputeTask ( 'computeForwardInvariantSet' , {
2242+ seed,
2243+ params : { a : params . a , b : params . b , epsilon : params . epsilon } ,
2244+ domain : viewRange ,
2245+ settings : {
2246+ boundaryPointCount : invariantSetState . boundaryPointCount ,
2247+ forwardIterations : invariantSetState . forwardIterations ,
2248+ } ,
2249+ } ) ;
2250+ if ( requestId !== invariantSetRequestIdRef . current ) return ;
2251+ setInvariantSetState ( previous => ( {
2252+ ...previous ,
2253+ isComputing : false ,
2254+ showResult : true ,
2255+ result,
2256+ error : null ,
2257+ } ) ) ;
2258+ } catch ( error ) {
2259+ if ( requestId !== invariantSetRequestIdRef . current ) return ;
2260+ setInvariantSetState ( previous => ( {
2261+ ...previous ,
2262+ isComputing : false ,
2263+ result : null ,
2264+ error : error instanceof Error ? error . message : String ( error ) ,
2265+ } ) ) ;
2266+ }
2267+ } , [
2268+ dynamicSystem ,
2269+ invariantSetState . boundaryPointCount ,
2270+ invariantSetState . forwardIterations ,
2271+ invariantSetState . seedMode ,
2272+ manifoldState . startPoint . x ,
2273+ manifoldState . startPoint . y ,
2274+ manifoldState . startPoint . nx ,
2275+ manifoldState . startPoint . ny ,
2276+ params . a ,
2277+ params . b ,
2278+ params . epsilon ,
2279+ runComputeTask ,
2280+ viewRange ,
2281+ ] ) ;
2282+
21922283 const computeGeometricOffsets = useCallback ( async ( ) => {
21932284 if ( dynamicSystem !== 'henon' || calculatedBoundaryBranches . length === 0 ) {
21942285 setGeometricOffsetState ( prev => ( {
@@ -2420,6 +2511,31 @@ const SetValuedViz = () => {
24202511 transitionViewRange
24212512 ] ) ;
24222513
2514+ useEffect ( ( ) => {
2515+ invariantSetRequestIdRef . current += 1 ;
2516+ setInvariantSetState ( previous => ( {
2517+ ...previous ,
2518+ isComputing : false ,
2519+ result : null ,
2520+ error : null ,
2521+ } ) ) ;
2522+ } , [ dynamicSystem , params . a , params . b , params . epsilon , viewRange ] ) ;
2523+
2524+ useEffect ( ( ) => {
2525+ if ( invariantSetState . seedMode !== 'manual' ) return ;
2526+ invariantSetRequestIdRef . current += 1 ;
2527+ setInvariantSetState ( previous => ( {
2528+ ...previous ,
2529+ isComputing : false ,
2530+ result : null ,
2531+ error : null ,
2532+ } ) ) ;
2533+ } , [
2534+ invariantSetState . seedMode ,
2535+ manifoldState . startPoint . x ,
2536+ manifoldState . startPoint . y ,
2537+ ] ) ;
2538+
24232539 useEffect ( ( ) => {
24242540 geometricOffsetRequestIdRef . current += 1 ;
24252541 inverseOffsetRequestIdRef . current += 1 ;
@@ -2445,7 +2561,7 @@ const SetValuedViz = () => {
24452561
24462562 const toRemove : THREE . Object3D [ ] = [ ] ;
24472563 scene . traverse ( child => {
2448- if ( child . userData . type === 'trajectory' || child . userData . type === 'manifold' || child . userData . type === 'boundaryLayer' || child . userData . type === 'boundarySamplePoints' || child . userData . type === 'noiseBall' || child . userData . type === 'geometricOffset' || child . userData . type === 'inverseGeometricOffset' || child . userData . type === 'fixedPoint' || child . userData . type === 'bde' ) {
2564+ if ( child . userData . type === 'trajectory' || child . userData . type === 'manifold' || child . userData . type === 'boundaryLayer' || child . userData . type === 'boundarySamplePoints' || child . userData . type === 'noiseBall' || child . userData . type === 'geometricOffset' || child . userData . type === 'inverseGeometricOffset' || child . userData . type === 'forwardInvariantSet' || child . userData . type === ' fixedPoint' || child . userData . type === 'bde' ) {
24492565 toRemove . push ( child ) ;
24502566 }
24512567 } ) ;
@@ -2511,6 +2627,37 @@ const SetValuedViz = () => {
25112627 scene . add ( samples ) ;
25122628 } ;
25132629
2630+ if ( invariantSetState . showResult && invariantSetState . result ?. point_sets . length ) {
2631+ // Iteration zero is the construction circle, not a forward-projected result.
2632+ const projectedPointSets = invariantSetState . result . point_sets . filter ( pointSet => (
2633+ pointSet . iteration > 0
2634+ ) ) ;
2635+ const projectedPoints = projectedPointSets . flatMap ( pointSet => pointSet . points ) ;
2636+ if ( projectedPoints . length > 0 ) {
2637+ const geometry = new THREE . BufferGeometry ( ) . setFromPoints (
2638+ projectedPoints . map ( point => new THREE . Vector3 ( point . x , point . y , 0.31 ) ) ,
2639+ ) ;
2640+ const material = new THREE . PointsMaterial ( {
2641+ color : new THREE . Color ( '#70a7c4' ) ,
2642+ size : 4 ,
2643+ sizeAttenuation : false ,
2644+ transparent : true ,
2645+ opacity : 0.9 ,
2646+ depthTest : false ,
2647+ depthWrite : false ,
2648+ } ) ;
2649+ const samples = new THREE . Points ( geometry , material ) ;
2650+ samples . renderOrder = 15 ;
2651+ samples . userData = {
2652+ type : 'forwardInvariantSet' ,
2653+ role : 'projected-point-samples' ,
2654+ pointSetCount : projectedPointSets . length ,
2655+ pointCount : projectedPoints . length ,
2656+ } ;
2657+ scene . add ( samples ) ;
2658+ }
2659+ }
2660+
25142661 if ( manifoldState . showUnstableManifold && hasBoundarySamples ) {
25152662 boundaryLayers . invariantBranches . forEach ( branch => {
25162663 if ( manifoldState . showBoundarySamplePoints ) {
@@ -2947,7 +3094,7 @@ const SetValuedViz = () => {
29473094 scene . add ( sphere ) ;
29483095 }
29493096
2950- } , [ manifoldState , geometricOffsetState , bdeState , dynamicSystem , type , viewRange , viewportRange , readViewportSize , boundaryLayers , hasBoundarySamples , hasVerifiedBoundaryCycles , params . epsilon , filters , periodicState . isReady , periodicState . orbits ] ) ;
3097+ } , [ manifoldState , geometricOffsetState , invariantSetState , bdeState , dynamicSystem , type , viewRange , viewportRange , readViewportSize , boundaryLayers , hasBoundarySamples , hasVerifiedBoundaryCycles , params . epsilon , filters , periodicState . isReady , periodicState . orbits ] ) ;
29513098
29523099 useEffect ( ( ) => {
29533100 if ( ! sceneRef . current ) return ;
@@ -3886,6 +4033,9 @@ const SetValuedViz = () => {
38864033 setManifoldState = { setManifoldState }
38874034 geometricOffsetState = { geometricOffsetState }
38884035 setGeometricOffsetState = { setGeometricOffsetState }
4036+ invariantSetState = { invariantSetState }
4037+ setInvariantSetState = { setInvariantSetState }
4038+ computeForwardInvariantSet = { computeForwardInvariantSet }
38894039 hasBoundarySamples = { hasBoundarySamples }
38904040 boundaryLayerError = { boundaryLayers . error }
38914041 boundarySampling = { {
@@ -3931,6 +4081,7 @@ const SetValuedViz = () => {
39314081 tooltip = { tooltip }
39324082 manifoldState = { manifoldState }
39334083 geometricOffsetState = { geometricOffsetState }
4084+ invariantSetState = { invariantSetState }
39344085 ulamState = { ulamState }
39354086 hasBoundarySamples = { hasBoundarySamples }
39364087 displayRange = { viewportRange }
0 commit comments