@@ -281,6 +281,54 @@ function _resolved_windowed_value_for_source(
281281 return nothing , false
282282end
283283
284+ function _resolved_interpolated_value_for_source (
285+ sim:: GraphSimulation ,
286+ source_scale:: String ,
287+ source_process:: Symbol ,
288+ source_var:: Symbol ,
289+ source_node_id:: Int ,
290+ t:: Float64
291+ )
292+ key = OutputKey (_default_scope (sim), source_scale, source_node_id, source_process, source_var)
293+ samples = get (sim. temporal_state. samples, key, nothing )
294+ isnothing (samples) && return nothing , false
295+ isempty (samples) && return nothing , false
296+
297+ prev_idx = findlast (s -> s[1 ] <= t + 1e-8 , samples)
298+ next_idx = findfirst (s -> s[1 ] >= t - 1e-8 , samples)
299+
300+ # Interpolate between known bracketing points when available.
301+ if ! isnothing (prev_idx) && ! isnothing (next_idx)
302+ t_prev, v_prev = samples[prev_idx]
303+ t_next, v_next = samples[next_idx]
304+ if isapprox (t_prev, t_next; atol= 1e-8 , rtol= 0.0 )
305+ return v_prev, true
306+ end
307+ if v_prev isa Real && v_next isa Real
308+ α = (t - t_prev) / (t_next - t_prev)
309+ return v_prev + α * (v_next - v_prev), true
310+ end
311+ return v_prev, true
312+ end
313+
314+ # Real-time fallback when no future sample exists yet:
315+ # use linear extrapolation from last two samples if possible, else hold-last.
316+ if ! isnothing (prev_idx)
317+ t_last, v_last = samples[prev_idx]
318+ if prev_idx >= 2
319+ t_prev, v_prev = samples[prev_idx - 1 ]
320+ if v_prev isa Real && v_last isa Real && ! isapprox (t_last, t_prev; atol= 1e-8 , rtol= 0.0 )
321+ α = (t - t_last) / (t_last - t_prev)
322+ return v_last + α * (v_last - v_prev), true
323+ end
324+ end
325+ return v_last, true
326+ end
327+
328+ # If only future data exists, use the earliest known value.
329+ return samples[1 ][2 ], true
330+ end
331+
284332function _assign_input_value! (st:: Status , input_var:: Symbol , value)
285333 current = st[input_var]
286334 if current isa RefVector && value isa AbstractVector
@@ -361,6 +409,67 @@ function _resolve_input_windowed(
361409 return nothing
362410end
363411
412+ function _resolve_input_interpolate (
413+ sim:: GraphSimulation ,
414+ node:: SoftDependencyNode ,
415+ st:: Status ,
416+ input_var:: Symbol ,
417+ source_scale:: String ,
418+ source_process:: Symbol ,
419+ source_var:: Symbol ,
420+ t:: Float64
421+ )
422+ source_statuses = get (status (sim), source_scale, nothing )
423+ isnothing (source_statuses) && return nothing
424+
425+ current_value = st[input_var]
426+ if current_value isa AbstractVector
427+ vals = Any[]
428+ for src_st in source_statuses
429+ src_node_id = node_id (src_st. node)
430+ v, ok = _resolved_interpolated_value_for_source (
431+ sim, source_scale, source_process, source_var, src_node_id, t
432+ )
433+ if ok
434+ push! (vals, v)
435+ elseif source_var in keys (src_st)
436+ push! (vals, src_st[source_var])
437+ end
438+ end
439+ length (vals) > 0 && _assign_input_value! (st, input_var, vals)
440+ return nothing
441+ end
442+
443+ consumer_node_id = node_id (st. node)
444+ v, ok = _resolved_interpolated_value_for_source (
445+ sim, source_scale, source_process, source_var, consumer_node_id, t
446+ )
447+ if ok
448+ _assign_input_value! (st, input_var, v)
449+ return nothing
450+ end
451+
452+ # Cross-scale scalar fallback: allow unique producer value at source scale.
453+ candidates = Any[]
454+ for src_st in source_statuses
455+ src_node_id = node_id (src_st. node)
456+ vv, found = _resolved_interpolated_value_for_source (
457+ sim, source_scale, source_process, source_var, src_node_id, t
458+ )
459+ found && push! (candidates, vv)
460+ end
461+ if length (candidates) == 1
462+ _assign_input_value! (st, input_var, only (candidates))
463+ elseif length (candidates) > 1
464+ error (
465+ " Ambiguous cross-scale source values for input `$(input_var) ` in process `$(node. process) ` at scale `$(node. scale) `. " ,
466+ " Please provide `InputBindings(...)` with explicit `scale`/source disambiguation."
467+ )
468+ end
469+
470+ return nothing
471+ end
472+
364473function _resolve_input_holdlast (sim:: GraphSimulation , node:: SoftDependencyNode , st:: Status , input_var:: Symbol , source_scale:: String , source_process:: Symbol , source_var:: Symbol , t:: Float64 )
365474 source_statuses = get (status (sim), source_scale, nothing )
366475 isnothing (source_statuses) && return nothing
@@ -447,6 +556,8 @@ function resolve_inputs_from_temporal_state!(sim::GraphSimulation, node::SoftDep
447556
448557 if policy isa HoldLast
449558 _resolve_input_holdlast (sim, node, st, input_var, source_scale, source_process, source_var, t)
559+ elseif policy isa Interpolate
560+ _resolve_input_interpolate (sim, node, st, input_var, source_scale, source_process, source_var, t)
450561 elseif policy isa Integrate || policy isa Aggregate
451562 _resolve_input_windowed (sim, node, st, input_var, source_scale, source_process, source_var, t_start, t, policy)
452563 end
0 commit comments