From 04c06f31bed96d585a51106e9ea2e0b708286b2a Mon Sep 17 00:00:00 2001 From: Heber Lima da Rocha Date: Thu, 6 Aug 2026 16:49:37 -0400 Subject: [PATCH] Fix rules engine treating behavior/signal synonyms as separate entries Hypothesis_Ruleset::add_behavior/find_behavior and Hypothesis_Rule::add_signal/ find_signal keyed their maps by the raw string from the rules file instead of the canonical, synonym-resolved name, even though find_behavior_index()/ find_signal_index() were already computing the canonical index just to validate it. Two rules for the same behavior spelled with different synonyms (e.g. "cycle entry" vs "exit from cycle phase 0") produced two separate Hypothesis_Rule objects instead of one merged multivariate Hill response, so whichever rule was applied last silently overwrote the other's contribution. Canonicalize behavior/signal names before they're used as map keys or compared by name, in add_behavior, find_behavior, add_signal, find_signal, set_half_max, set_hill_power, and set_response. Also fix signals_map[signal] = signals_map.size(), which used map size as a proxy for vector index and could collide when the same signal was added twice with opposite responses in one rule; and drop a dead code path in add_rule that would have fought the new canonicalization. Co-Authored-By: Claude Sonnet 5 --- core/PhysiCell_rules.cpp | 53 +++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/core/PhysiCell_rules.cpp b/core/PhysiCell_rules.cpp index 3b73a38a3..642c15449 100644 --- a/core/PhysiCell_rules.cpp +++ b/core/PhysiCell_rules.cpp @@ -250,8 +250,9 @@ void Hypothesis_Rule::English_display_HTML( std::ostream& os ) void Hypothesis_Rule::add_signal( std::string signal , double half_max , double hill_power , std::string response ) { + int signal_index = find_signal_index(signal); // check: is this a valid signal? (is it in the dictionary?) - if( find_signal_index(signal) < 0 ) + if( signal_index < 0 ) { std::cout << "Error! Attempted to add signal " << signal << " which is not in the dictionary." << std::endl; std::cout << "Either fix your model or add the missing signal to the simulation." << std::endl; @@ -261,6 +262,9 @@ void Hypothesis_Rule::add_signal( std::string signal , double half_max , double exit(-1); } + // canonicalize the name so that synonyms of the same signal are treated as one signal + signal = signal_name( signal_index ); + // check to see if the signal and response already there int n = find_signal(signal); bool bResponse = false; // true if up-regulate, false if down @@ -278,8 +282,8 @@ void Hypothesis_Rule::add_signal( std::string signal , double half_max , double exit(-1); } - // add the signal; - signals_map[signal] = signals_map.size(); + // add the signal; + signals_map[signal] = signals.size(); signals.push_back( signal ); half_maxes.push_back( half_max ); @@ -411,6 +415,11 @@ void Hypothesis_Rule::sync_to_cell_definition( std::string cell_name ) int Hypothesis_Rule::find_signal( std::string name ) { + int index = find_signal_index( name ); + if( index < 0 ) + { return -1; } + name = signal_name( index ); + auto search = signals_map.find(name); if( search == signals_map.end() ) @@ -420,7 +429,11 @@ int Hypothesis_Rule::find_signal( std::string name ) } void Hypothesis_Rule::set_half_max( std::string name , double hm ) -{ +{ + int index = find_signal_index( name ); + if( index >= 0 ) + { name = signal_name( index ); } + int n = find_signal( name ); if( n < 0 ) { return; } @@ -448,6 +461,10 @@ void Hypothesis_Rule::set_half_max( std::string name , double hm ) void Hypothesis_Rule::set_hill_power( std::string name , double hp ) { + int index = find_signal_index( name ); + if( index >= 0 ) + { name = signal_name( index ); } + int n = find_signal( name ); if( n < 0 ) { return; } @@ -474,6 +491,10 @@ void Hypothesis_Rule::set_hill_power( std::string name , double hp ) void Hypothesis_Rule::set_response( std::string name , std::string response ) { + int index = find_signal_index( name ); + if( index >= 0 ) + { name = signal_name( index ); } + int n = find_signal( name ); if( n < 0 ) { return; } @@ -757,7 +778,8 @@ void Hypothesis_Ruleset::sync_to_cell_definition( Cell_Definition* pCD ) Hypothesis_Rule* Hypothesis_Ruleset::add_behavior( std::string behavior , double min_behavior, double max_behavior ) { // check: is this a valid signal? (is it in the dictionary?) - if( find_behavior_index(behavior) < 0 ) + int behavior_index = find_behavior_index(behavior); + if( behavior_index < 0 ) { std::cout << "Warning! Attempted to add behavior " << behavior << " which is not in the dictionary." << std::endl; std::cout << "Either fix your model or add the missing behavior to the simulation." << std::endl; @@ -767,6 +789,10 @@ Hypothesis_Rule* Hypothesis_Ruleset::add_behavior( std::string behavior , double exit(-1); } + // canonicalize the name so that synonyms of the same behavior share a single rule + // (and hence a single multivariate Hill response) instead of one overwriting another + behavior = behavior_name( behavior_index ); + // first, check. Is there already a ruleset? auto search = rules_map.find( behavior ); @@ -812,6 +838,11 @@ void Hypothesis_Ruleset::sync_to_cell_definition( std::string cell_name ) Hypothesis_Rule* Hypothesis_Ruleset::find_behavior( std::string name ) { + int index = find_behavior_index( name ); + if( index < 0 ) + { return NULL; } + name = behavior_name( index ); + auto search = rules_map.find( name); if( search == rules_map.end() ) { @@ -903,12 +934,6 @@ void add_rule( std::string cell_type, std::string signal, std::string behavior , exit(-1); } - if( pHRS->find_behavior(behavior) ) - { - if( (*pHRS)[behavior].behavior != behavior ) - { (*pHRS)[behavior].behavior = behavior; std::cout << "wha?" << std::endl; } - } - pHRS->add_behavior(behavior); (*pHRS)[behavior].add_signal(signal,response); @@ -934,12 +959,6 @@ void add_rule( std::string cell_type, std::string signal, std::string behavior , exit(-1); } - if( pHRS->find_behavior(behavior) ) - { - if( (*pHRS)[behavior].behavior != behavior ) - { (*pHRS)[behavior].behavior = behavior; std::cout << "wha?" << std::endl; } - } - pHRS->add_behavior(behavior); (*pHRS)[behavior].add_signal(signal,response);