Skip to content

Commit 93c065c

Browse files
committed
Replace set with ordered list + shadow set for blocks, connections, events
1 parent 0d52b54 commit 93c065c

3 files changed

Lines changed: 91 additions & 66 deletions

File tree

src/pathsim/simulation.py

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ class Simulation:
153153
get attributes and access to intermediate evaluation stages
154154
logger : logging.Logger
155155
global simulation logger
156-
_blocks_dyn : set[Block]
157-
blocks with internal ´Solver´ instances (stateful)
158-
_blocks_evt : set[Block]
159-
blocks with internal events (discrete time, eventful)
156+
_blocks_dyn : list[Block]
157+
blocks with internal ´Solver´ instances (stateful)
158+
_blocks_evt : list[Block]
159+
blocks with internal events (discrete time, eventful)
160160
_active : bool
161161
flag for setting the simulation as active, used for interrupts
162162
"""
@@ -176,10 +176,13 @@ def __init__(
176176
**solver_kwargs
177177
):
178178

179-
#system definition
180-
self.blocks = set()
181-
self.connections = set()
182-
self.events = set()
179+
#system definition (ordered lists with shadow sets for O(1) lookup)
180+
self.blocks = []
181+
self._block_set = set()
182+
self.connections = []
183+
self._conn_set = set()
184+
self.events = []
185+
self._event_set = set()
183186

184187
#simulation timestep and bounds
185188
self.dt = dt
@@ -215,10 +218,12 @@ def __init__(
215218
self.time = 0.0
216219

217220
#collection of blocks with internal ODE solvers
218-
self._blocks_dyn = set()
221+
self._blocks_dyn = []
222+
self._blocks_dyn_set = set()
219223

220224
#collection of blocks with internal events
221-
self._blocks_evt = set()
225+
self._blocks_evt = []
226+
self._blocks_evt_set = set()
222227

223228
#flag for setting the simulation active
224229
self._active = True
@@ -269,9 +274,9 @@ def __contains__(self, other):
269274
bool
270275
"""
271276
return (
272-
other in self.blocks or
273-
other in self.connections or
274-
other in self.events
277+
other in self._block_set or
278+
other in self._conn_set or
279+
other in self._event_set
275280
)
276281

277282

@@ -480,7 +485,7 @@ def add_block(self, block):
480485
"""
481486

482487
#check if block already in block list
483-
if block in self.blocks:
488+
if block in self._block_set:
484489
_msg = f"block {block} already part of simulation"
485490
self.logger.error(_msg)
486491
raise ValueError(_msg)
@@ -490,14 +495,17 @@ def add_block(self, block):
490495

491496
#add to dynamic list if solver was initialized
492497
if block.engine:
493-
self._blocks_dyn.add(block)
498+
self._blocks_dyn.append(block)
499+
self._blocks_dyn_set.add(block)
494500

495501
#add to eventful list if internal events
496502
if block.events:
497-
self._blocks_evt.add(block)
503+
self._blocks_evt.append(block)
504+
self._blocks_evt_set.add(block)
498505

499506
#add block to global blocklist
500-
self.blocks.add(block)
507+
self.blocks.append(block)
508+
self._block_set.add(block)
501509

502510
#mark graph for rebuild
503511
if self.graph:
@@ -517,19 +525,24 @@ def remove_block(self, block):
517525
"""
518526

519527
#check if block is in block list
520-
if block not in self.blocks:
528+
if block not in self._block_set:
521529
_msg = f"block {block} not part of simulation"
522530
self.logger.error(_msg)
523531
raise ValueError(_msg)
524532

525533
#remove from global blocklist
526-
self.blocks.discard(block)
534+
self.blocks.remove(block)
535+
self._block_set.discard(block)
527536

528537
#remove from dynamic list
529-
self._blocks_dyn.discard(block)
538+
if block in self._blocks_dyn_set:
539+
self._blocks_dyn.remove(block)
540+
self._blocks_dyn_set.discard(block)
530541

531542
#remove from eventful list
532-
self._blocks_evt.discard(block)
543+
if block in self._blocks_evt_set:
544+
self._blocks_evt.remove(block)
545+
self._blocks_evt_set.discard(block)
533546

534547
#mark graph for rebuild
535548
if self.graph:
@@ -549,13 +562,14 @@ def add_connection(self, connection):
549562
"""
550563

551564
#check if connection already in connection list
552-
if connection in self.connections:
565+
if connection in self._conn_set:
553566
_msg = f"{connection} already part of simulation"
554567
self.logger.error(_msg)
555568
raise ValueError(_msg)
556569

557570
#add connection to global connection list
558-
self.connections.add(connection)
571+
self.connections.append(connection)
572+
self._conn_set.add(connection)
559573

560574
#mark graph for rebuild
561575
if self.graph:
@@ -575,13 +589,14 @@ def remove_connection(self, connection):
575589
"""
576590

577591
#check if connection is in connection list
578-
if connection not in self.connections:
592+
if connection not in self._conn_set:
579593
_msg = f"{connection} not part of simulation"
580594
self.logger.error(_msg)
581595
raise ValueError(_msg)
582596

583597
#remove from global connection list
584-
self.connections.discard(connection)
598+
self.connections.remove(connection)
599+
self._conn_set.discard(connection)
585600

586601
#mark graph for rebuild
587602
if self.graph:
@@ -600,13 +615,14 @@ def add_event(self, event):
600615
"""
601616

602617
#check if event already in event list
603-
if event in self.events:
618+
if event in self._event_set:
604619
_msg = f"{event} already part of simulation"
605620
self.logger.error(_msg)
606621
raise ValueError(_msg)
607622

608623
#add event to global event list
609-
self.events.add(event)
624+
self.events.append(event)
625+
self._event_set.add(event)
610626

611627

612628
def remove_event(self, event):
@@ -621,13 +637,14 @@ def remove_event(self, event):
621637
"""
622638

623639
#check if event is in event list
624-
if event not in self.events:
640+
if event not in self._event_set:
625641
_msg = f"{event} not part of simulation"
626642
self.logger.error(_msg)
627643
raise ValueError(_msg)
628644

629645
#remove from global event list
630-
self.events.discard(event)
646+
self.events.remove(event)
647+
self._event_set.discard(event)
631648

632649

633650
# system assembly -------------------------------------------------------------
@@ -685,10 +702,11 @@ def _check_blocks_are_managed(self):
685702
conn_blocks.update(conn.get_blocks())
686703

687704
# Check subset actively managed
688-
if not conn_blocks.issubset(self.blocks):
689-
self.logger.warning(
690-
f"{blk} in 'connections' but not in 'blocks'!"
691-
)
705+
for blk in conn_blocks:
706+
if blk not in self._block_set:
707+
self.logger.warning(
708+
f"{blk} in 'connections' but not in 'blocks'!"
709+
)
692710

693711

694712
# solver management -----------------------------------------------------------
@@ -719,13 +737,15 @@ def _set_solver(self, Solver=None, **solver_kwargs):
719737
self.engine = self.Solver()
720738

721739
#iterate all blocks and set integration engines with tolerances
722-
self._blocks_dyn = set()
740+
self._blocks_dyn = []
741+
self._blocks_dyn_set = set()
723742
for block in self.blocks:
724743
block.set_solver(self.Solver, self.engine, **self.solver_kwargs)
725-
744+
726745
#add dynamic blocks to list
727746
if block.engine:
728-
self._blocks_dyn.add(block)
747+
self._blocks_dyn.append(block)
748+
self._blocks_dyn_set.add(block)
729749

730750
#logging message
731751
self.logger.info(

src/pathsim/subsystem.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -181,27 +181,28 @@ def __init__(self,
181181
#internal algebraic loop solvers -> initialized later
182182
self.boosters = None
183183

184-
#internal connecions
185-
self.connections = set()
186-
if connections:
187-
self.connections.update(connections)
188-
184+
#internal connecions (ordered list with shadow set for O(1) lookup)
185+
self.connections = list(connections) if connections else []
186+
self._conn_set = set(self.connections)
187+
189188
#collect and organize internal blocks
190-
self.blocks = set()
191-
self.interface = None
189+
self.blocks = []
190+
self._block_set = set()
191+
self.interface = None
192192

193193
if blocks:
194194
for block in blocks:
195-
if isinstance(block, Interface):
196-
195+
if isinstance(block, Interface):
196+
197197
if self.interface is not None:
198198
#interface block is already defined
199199
raise ValueError("Subsystem can only have one 'Interface' block!")
200-
200+
201201
self.interface = block
202-
else:
202+
else:
203203
#regular blocks
204-
self.blocks.add(block)
204+
self.blocks.append(block)
205+
self._block_set.add(block)
205206

206207
#check if interface is defined
207208
if self.interface is None:
@@ -252,7 +253,7 @@ def __contains__(self, other):
252253
-------
253254
bool
254255
"""
255-
return other in self.blocks or other in self.connections
256+
return other in self._block_set or other in self._conn_set
256257

257258

258259
# adding and removing system components ---------------------------------------------------
@@ -267,7 +268,7 @@ def add_block(self, block):
267268
block : Block
268269
block to add to the subsystem
269270
"""
270-
if block in self.blocks:
271+
if block in self._block_set:
271272
raise ValueError(f"block {block} already part of subsystem")
272273

273274
#initialize solver if available
@@ -276,7 +277,8 @@ def add_block(self, block):
276277
if block.engine:
277278
self._blocks_dyn.append(block)
278279

279-
self.blocks.add(block)
280+
self.blocks.append(block)
281+
self._block_set.add(block)
280282

281283
if self.graph:
282284
self._graph_dirty = True
@@ -292,10 +294,11 @@ def remove_block(self, block):
292294
block : Block
293295
block to remove from the subsystem
294296
"""
295-
if block not in self.blocks:
297+
if block not in self._block_set:
296298
raise ValueError(f"block {block} not part of subsystem")
297299

298-
self.blocks.discard(block)
300+
self.blocks.remove(block)
301+
self._block_set.discard(block)
299302

300303
#remove from dynamic list
301304
if hasattr(self, '_blocks_dyn') and block in self._blocks_dyn:
@@ -315,10 +318,11 @@ def add_connection(self, connection):
315318
connection : Connection
316319
connection to add to the subsystem
317320
"""
318-
if connection in self.connections:
321+
if connection in self._conn_set:
319322
raise ValueError(f"{connection} already part of subsystem")
320323

321-
self.connections.add(connection)
324+
self.connections.append(connection)
325+
self._conn_set.add(connection)
322326

323327
if self.graph:
324328
self._graph_dirty = True
@@ -334,10 +338,11 @@ def remove_connection(self, connection):
334338
connection : Connection
335339
connection to remove from the subsystem
336340
"""
337-
if connection not in self.connections:
341+
if connection not in self._conn_set:
338342
raise ValueError(f"{connection} not part of subsystem")
339343

340-
self.connections.discard(connection)
344+
self.connections.remove(connection)
345+
self._conn_set.discard(connection)
341346

342347
if self.graph:
343348
self._graph_dirty = True
@@ -386,7 +391,7 @@ def _assemble_graph(self):
386391
for block in self.blocks:
387392
block.inputs.reset()
388393

389-
self.graph = Graph({*self.blocks, self.interface}, self.connections)
394+
self.graph = Graph([*self.blocks, self.interface], self.connections)
390395
self._graph_dirty = False
391396

392397
#create boosters for loop closing connections

tests/pathsim/test_simulation.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ def test_init_default(self):
5252

5353
#test default initialization
5454
Sim = Simulation(log=False)
55-
self.assertEqual(Sim.blocks, set())
56-
self.assertEqual(Sim.connections, set())
57-
self.assertEqual(Sim.events, set())
55+
self.assertEqual(Sim.blocks, [])
56+
self.assertEqual(Sim.connections, [])
57+
self.assertEqual(Sim.events, [])
5858
self.assertEqual(Sim.dt, SIM_TIMESTEP)
5959
self.assertEqual(Sim.dt_min, SIM_TIMESTEP_MIN)
6060
self.assertEqual(Sim.dt_max, SIM_TIMESTEP_MAX)
@@ -130,12 +130,12 @@ def test_add_block(self):
130130

131131
Sim = Simulation(log=False)
132132

133-
self.assertEqual(Sim.blocks, set())
133+
self.assertEqual(Sim.blocks, [])
134134

135135
#test adding a block
136136
B1 = Block()
137137
Sim.add_block(B1)
138-
self.assertEqual(Sim.blocks, {B1})
138+
self.assertEqual(Sim.blocks, [B1])
139139

140140
#test adding the same block again
141141
with self.assertRaises(ValueError):
@@ -153,17 +153,17 @@ def test_add_connection(self):
153153
log=False
154154
)
155155

156-
self.assertEqual(Sim.connections, {C1})
156+
self.assertEqual(Sim.connections, [C1])
157157

158158
#test adding a connection
159159
C2 = Connection(B2, B3)
160160
Sim.add_connection(C2)
161-
self.assertEqual(Sim.connections, {C1, C2})
161+
self.assertEqual(Sim.connections, [C1, C2])
162162

163163
#test adding the same connection again
164164
with self.assertRaises(ValueError):
165165
Sim.add_connection(C2)
166-
self.assertEqual(Sim.connections, {C1, C2})
166+
self.assertEqual(Sim.connections, [C1, C2])
167167

168168

169169
def test_set_solver(self):

0 commit comments

Comments
 (0)