@@ -250,6 +250,25 @@ function add_parameter_dependencies!(sys::AbstractSystem, varmap::AbstractDict)
250250    add_observed_equations! (varmap, parameter_dependencies (sys))
251251end 
252252
253+ struct  UnexpectedSymbolicValueInVarmap <:  Exception 
254+     sym:: Any 
255+     val:: Any 
256+ end 
257+ 
258+ function  Base. showerror (io:: IO , err:: UnexpectedSymbolicValueInVarmap )
259+     println (io,
260+         """ 
261+         Found symbolic value $(err. val)  for variable $(err. sym) . You may be missing an \ 
262+         initial condition or have cyclic initial conditions. If this is intended, pass \ 
263+         `symbolic_u0 = true`. In case the initial conditions are not cyclic but \ 
264+         require more substitutions to resolve, increase `substitution_limit`. To report \ 
265+         cycles in initial conditions of unknowns/parameters, pass \ 
266+         `warn_cyclic_dependency = true`. If the cycles are still not reported, you \ 
267+         may need to pass a larger value for `circular_dependency_max_cycle_length` \ 
268+         or `circular_dependency_max_cycles`. 
269+         """  )
270+ end 
271+ 
253272""" 
254273    $(TYPEDSIGNATURES)  
255274
@@ -278,6 +297,12 @@ function better_varmap_to_vars(varmap::AbstractDict, vars::Vector;
278297        isempty (missing_vars) ||  throw (MissingVariablesError (missing_vars))
279298    end 
280299    vals =  map (x ->  varmap[x], vars)
300+     if  ! allow_symbolic
301+         for  (sym, val) in  zip (vars, vals)
302+             symbolic_type (val) ==  NotSymbolic () &&  continue 
303+             throw (UnexpectedSymbolicValueInVarmap (sym, val))
304+         end 
305+     end 
281306
282307    if  container_type <:  Union{AbstractDict, Tuple, Nothing, SciMLBase.NullParameters} 
283308        container_type =  Array
@@ -298,17 +323,68 @@ function better_varmap_to_vars(varmap::AbstractDict, vars::Vector;
298323    end 
299324end 
300325
326+ """ 
327+     $(TYPEDSIGNATURES)  
328+ 
329+ Check if any of the substitution rules in `varmap` lead to cycles involving 
330+ variables in `vars`. Return a vector of vectors containing all the variables 
331+ in each cycle. 
332+ 
333+ Keyword arguments: 
334+ - `max_cycle_length`: The maximum length (number of variables) of detected cycles. 
335+ - `max_cycles`: The maximum number of cycles to report. 
336+ """ 
337+ function  check_substitution_cycles (
338+         varmap:: AbstractDict , vars; max_cycle_length =  length (varmap), max_cycles =  10 )
339+     #  ordered set so that `vars` are the first `k` in the list
340+     allvars =  OrderedSet {Any} (vars)
341+     union! (allvars, keys (varmap))
342+     allvars =  collect (allvars)
343+     var_to_idx =  Dict (allvars .=>  eachindex (allvars))
344+     graph =  SimpleDiGraph (length (allvars))
345+ 
346+     buffer =  Set ()
347+     for  (k, v) in  varmap
348+         kidx =  var_to_idx[k]
349+         if  symbolic_type (v) !=  NotSymbolic ()
350+             vars! (buffer, v)
351+             for  var in  buffer
352+                 haskey (var_to_idx, var) ||  continue 
353+                 add_edge! (graph, kidx, var_to_idx[var])
354+             end 
355+         elseif  v isa  AbstractArray
356+             for  val in  v
357+                 vars! (buffer, val)
358+             end 
359+             for  var in  buffer
360+                 haskey (var_to_idx, var) ||  continue 
361+                 add_edge! (graph, kidx, var_to_idx[var])
362+             end 
363+         end 
364+         empty! (buffer)
365+     end 
366+ 
367+     #  detect at most 100 cycles involving at most `length(varmap)` vertices
368+     cycles =  Graphs. simplecycles_limited_length (graph, max_cycle_length, max_cycles)
369+     #  only count those which contain variables in `vars`
370+     filter! (Base. Fix1 (any, <= (length (vars))), cycles)
371+ 
372+     map (cycles) do  cycle
373+         map (Base. Fix1 (getindex, allvars), cycle)
374+     end 
375+ end 
376+ 
301377""" 
302378    $(TYPEDSIGNATURES)  
303379
304380Performs symbolic substitution on the values in `varmap` for the keys in `vars`, using 
305381`varmap` itself as the set of substitution rules. If an entry in `vars` is not a key 
306382in `varmap`, it is ignored. 
307383""" 
308- function  evaluate_varmap! (varmap:: AbstractDict , vars)
384+ function  evaluate_varmap! (varmap:: AbstractDict , vars; limit  =   100 )
309385    for  k in  vars
310386        haskey (varmap, k) ||  continue 
311-         varmap[k] =  fixpoint_sub (varmap[k], varmap)
387+         varmap[k] =  fixpoint_sub (varmap[k], varmap; maxiters  =  limit )
312388    end 
313389end 
314390
@@ -407,6 +483,14 @@ Keyword arguments:
407483  length of `u0` vector for consistency. If `false`, do not check with equations. This is 
408484  forwarded to `check_eqs_u0` 
409485- `symbolic_u0` allows the returned `u0` to be an array of symbolics. 
486+ - `warn_cyclic_dependency`: Whether to emit a warning listing out cycles in initial 
487+   conditions provided for unknowns and parameters. 
488+ - `circular_dependency_max_cycle_length`: Maximum length of cycle to check for. 
489+   Only applicable if `warn_cyclic_dependency == true`. 
490+ - `circular_dependency_max_cycles`: Maximum number of cycles to check for. 
491+   Only applicable if `warn_cyclic_dependency == true`. 
492+ - `substitution_limit`: The number times to substitute initial conditions into each 
493+   other to attempt to arrive at a numeric value. 
410494
411495All other keyword arguments are passed as-is to `constructor`. 
412496""" 
@@ -416,7 +500,11 @@ function process_SciMLProblem(
416500        warn_initialize_determined =  true , initialization_eqs =  [],
417501        eval_expression =  false , eval_module =  @__MODULE__ , fully_determined =  false ,
418502        check_initialization_units =  false , tofloat =  true , use_union =  false ,
419-         u0_constructor =  identity, du0map =  nothing , check_length =  true , symbolic_u0 =  false , kwargs... )
503+         u0_constructor =  identity, du0map =  nothing , check_length =  true ,
504+         symbolic_u0 =  false , warn_cyclic_dependency =  false ,
505+         circular_dependency_max_cycle_length =  length (all_symbols (sys)),
506+         circular_dependency_max_cycles =  10 ,
507+         substitution_limit =  100 , kwargs... )
420508    dvs =  unknowns (sys)
421509    ps =  parameters (sys)
422510    iv =  has_iv (sys) ?  get_iv (sys) :  nothing 
@@ -466,7 +554,8 @@ function process_SciMLProblem(
466554            initializeprob =  ModelingToolkit. InitializationProblem (
467555                sys, t, u0map, pmap; guesses, warn_initialize_determined,
468556                initialization_eqs, eval_expression, eval_module, fully_determined,
469-                 check_units =  check_initialization_units)
557+                 warn_cyclic_dependency, check_units =  check_initialization_units,
558+                 circular_dependency_max_cycle_length, circular_dependency_max_cycles)
470559            initializeprobmap =  getu (initializeprob, unknowns (sys))
471560
472561            punknowns =  [p
@@ -503,7 +592,20 @@ function process_SciMLProblem(
503592    add_observed! (sys, op)
504593    add_parameter_dependencies! (sys, op)
505594
506-     evaluate_varmap! (op, dvs)
595+     if  warn_cyclic_dependency
596+         cycles =  check_substitution_cycles (
597+             op, dvs; max_cycle_length =  circular_dependency_max_cycle_length,
598+             max_cycles =  circular_dependency_max_cycles)
599+         if  ! isempty (cycles)
600+             buffer =  IOBuffer ()
601+             for  cycle in  cycles
602+                 println (buffer, cycle)
603+             end 
604+             msg =  String (take! (buffer))
605+             @warn  " Cycles in unknowns:\n $msg " 
606+         end 
607+     end 
608+     evaluate_varmap! (op, dvs; limit =  substitution_limit)
507609
508610    u0 =  better_varmap_to_vars (
509611        op, dvs; tofloat =  true , use_union =  false ,
@@ -515,7 +617,20 @@ function process_SciMLProblem(
515617
516618    check_eqs_u0 (eqs, dvs, u0; check_length, kwargs... )
517619
518-     evaluate_varmap! (op, ps)
620+     if  warn_cyclic_dependency
621+         cycles =  check_substitution_cycles (
622+             op, ps; max_cycle_length =  circular_dependency_max_cycle_length,
623+             max_cycles =  circular_dependency_max_cycles)
624+         if  ! isempty (cycles)
625+             buffer =  IOBuffer ()
626+             for  cycle in  cycles
627+                 println (buffer, cycle)
628+             end 
629+             msg =  String (take! (buffer))
630+             @warn  " Cycles in parameters:\n $msg " 
631+         end 
632+     end 
633+     evaluate_varmap! (op, ps; limit =  substitution_limit)
519634    if  is_split (sys)
520635        p =  MTKParameters (sys, op)
521636    else 
0 commit comments