@@ -136,81 +136,99 @@ private List<ILsaCondition> getSAPEREConditions() {
136136 @ Override
137137 public void execute () {
138138 if (possibleMatches .isEmpty ()) {
139- for (final ILsaAction a : getSAPEREActions ()) {
140- a .setExecutionContext (null , validNodes );
141- a .execute ();
142- }
139+ executeActions (null );
143140 return ;
144141 }
145142 final Position <?> nodePosCache = modifiesOnlyLocally ? environment .getPosition (getNode ()) : null ;
146143 final List <? extends ILsaMolecule > localContentCache = modifiesOnlyLocally
147144 ? new ArrayList <>(getLsaNode ().getLsaSpace ())
148145 : null ;
149- Map <HashString , ITreeNode <?>> matches = null ;
150- Map <ILsaNode , List <ILsaMolecule >> toRemove = null ;
151- /*
152- * If there is infinite propensity, the last match added is the one to
153- * choose, since it is the one which generated the "infinity" value.
154- */
155- if (totalPropensity == Double .POSITIVE_INFINITY ) {
156- final int index = possibleMatches .size () - 1 ;
157- matches = possibleMatches .get (index );
158- toRemove = possibleRemove .get (index );
159- } else if (numericRate ()) {
160- /*
161- * If the rate is numeric, the choice is just random
162- */
163- final int index = Math .abs (rng .nextInt ()) % possibleMatches .size ();
164- matches = possibleMatches .get (index );
165- toRemove = possibleRemove .get (index );
166- } else {
167- /*
168- * Otherwise, the matches must be chosen randomly using their
169- * propensities
170- */
171- final double index = rng .nextDouble () * totalPropensity ;
172- double sum = 0 ;
173- for (int i = 0 ; matches == null ; i ++) {
174- sum += propensities .get (i );
175- if (sum > index ) {
176- matches = possibleMatches .get (i );
177- toRemove = possibleRemove .get (i );
178- }
179- }
180- }
146+ final int selectedMatchIndex = selectMatchIndex ();
147+ final Map <HashString , ITreeNode <?>> matches = possibleMatches .get (selectedMatchIndex );
181148 /*
182149 * The matched LSAs must be removed from the local space, if no action
183150 * added them back.
184151 */
185- for (final Entry <ILsaNode , List <ILsaMolecule >> entry : toRemove .entrySet ()) {
186- final ILsaNode n = entry .getKey ();
187- for (final ILsaMolecule m : entry .getValue ()) {
188- n .removeConcentration (m );
189- }
190- }
152+ removeMatchedMolecules (possibleRemove .get (selectedMatchIndex ));
191153 /*
192154 * #T Must be loaded by the reaction, which is the only structure aware
193155 * of the time. Other special values (#NEIG, #O, #D) will be allocated
194156 * inside the actions.
195157 */
196158 matches .put (LsaMolecule .SYN_T , new NumTreeNode (getTau ().toDouble ()));
197- for (final ILsaAction a : getSAPEREActions ()) {
198- a .setExecutionContext (matches , validNodes );
199- a .execute ();
159+ executeActions (matches );
160+ /*
161+ * Empty action optimization
162+ */
163+ updateEmptyExecutionStatus (nodePosCache , localContentCache );
164+ }
165+
166+ private void executeActions (final Map <HashString , ITreeNode <?>> matches ) {
167+ for (final ILsaAction action : getSAPEREActions ()) {
168+ action .setExecutionContext (matches , validNodes );
169+ action .execute ();
200170 }
171+ }
201172
173+ private void removeMatchedMolecules (final Map <ILsaNode , List <ILsaMolecule >> toRemove ) {
174+ for (final Entry <ILsaNode , List <ILsaMolecule >> entry : toRemove .entrySet ()) {
175+ final ILsaNode node = entry .getKey ();
176+ for (final ILsaMolecule molecule : entry .getValue ()) {
177+ node .removeConcentration (molecule );
178+ }
179+ }
180+ }
181+
182+ private int selectMatchIndex () {
202183 /*
203- * Empty action optimization
184+ * If there is infinite propensity, the last match added is the one to
185+ * choose, since it is the one which generated the "infinity" value.
204186 */
205- if (modifiesOnlyLocally ) {
206- final ILsaNode n = getLsaNode ();
207- if (Objects .requireNonNull (nodePosCache ).equals (environment .getPosition (getNode ()))) {
208- final List <? extends ILsaMolecule > contents = n .getLsaSpace ();
209- if (contents .size () == Objects .requireNonNull (localContentCache ).size ()) {
210- emptyExecution = localContentCache .containsAll (contents );
211- }
187+ if (totalPropensity == Double .POSITIVE_INFINITY ) {
188+ return possibleMatches .size () - 1 ;
189+ }
190+ /*
191+ * If the rate is numeric, the choice is just random
192+ */
193+ if (numericRate ()) {
194+ return rng .nextInt (possibleMatches .size ());
195+ }
196+ /*
197+ * Otherwise, the matches must be chosen randomly using their
198+ * propensities
199+ */
200+ return selectWeightedMatchIndex ();
201+ }
202+
203+ private int selectWeightedMatchIndex () {
204+ final double selectedPropensity = rng .nextDouble () * totalPropensity ;
205+ double cumulativePropensity = 0 ;
206+ for (int i = 0 ; i < propensities .size (); i ++) {
207+ cumulativePropensity += propensities .get (i );
208+ if (cumulativePropensity > selectedPropensity ) {
209+ return i ;
212210 }
213211 }
212+ // Floating-point rounding can cause selectedPropensity == totalPropensity;
213+ // fall back to the last bucket in that case.
214+ return propensities .size () - 1 ;
215+ }
216+
217+ private void updateEmptyExecutionStatus (
218+ final Position <?> nodePositionBeforeExecution ,
219+ final List <? extends ILsaMolecule > localContentBeforeExecution
220+ ) {
221+ if (!modifiesOnlyLocally || nodePositionChanged (nodePositionBeforeExecution )) {
222+ return ;
223+ }
224+ final List <? extends ILsaMolecule > contents = getLsaNode ().getLsaSpace ();
225+ if (contents .size () == Objects .requireNonNull (localContentBeforeExecution ).size ()) {
226+ emptyExecution = localContentBeforeExecution .containsAll (contents );
227+ }
228+ }
229+
230+ private boolean nodePositionChanged (final Position <?> nodePositionBeforeExecution ) {
231+ return !Objects .requireNonNull (nodePositionBeforeExecution ).equals (environment .getPosition (getNode ()));
214232 }
215233
216234 /**
0 commit comments