Simulation Algorithms

Similar documents
CS 372: Computational Geometry Lecture 3 Line Segment Intersection

Computational Geometry

CS61B Fall 2014 Guerrilla Section 2 This worksheet is quite long. Longer than we ll cover in guerrilla section.

CSI33 Data Structures

/633 Introduction to Algorithms Lecturer: Michael Dinitz Topic: Priority Queues / Heaps Date: 9/27/17

Lecture 16: Binary Search Trees

Introduction to Algorithms

Binary Search Tree. Revised based on textbook author s notes.

/633 Introduction to Algorithms Lecturer: Michael Dinitz Topic: Sorting lower bound and Linear-time sorting Date: 9/19/17

Figure 1: A complete binary tree.

! Tree: set of nodes and directed edges. ! Parent: source node of directed edge. ! Child: terminal node of directed edge

Integer Algorithms and Data Structures

Fundamentals of Programming (Python) Object-Oriented Programming. Ali Taheri Sharif University of Technology Spring 2018

Binary Search Trees. EECS 214, Fall 2018

Introduction to Computer Science and Programming for Astronomers

Quiz 1 Solutions. (a) f(n) = n g(n) = log n Circle all that apply: f = O(g) f = Θ(g) f = Ω(g)

Balanced Binary Search Trees. Victor Gao

Lecture 3: Art Gallery Problems and Polygon Triangulation

External-Memory Algorithms with Applications in GIS - (L. Arge) Enylton Machado Roberto Beauclair

Short Python function/method descriptions:

More about The Competition

1 Ordered Sets and Tables

Lecture 9 March 4, 2010

External Memory Algorithms for Geometric Problems. Piotr Indyk (slides partially by Lars Arge and Jeff Vitter)

Trees. Tree Structure Binary Tree Tree Traversals

9/24/ Hash functions

Notes and Answers to Homework Exam 1, Geometric Algorithms, 2017

Why study algorithms? CS 561, Lecture 1. Today s Outline. Why study algorithms? (II)

CSE 332: Data Structures & Parallelism Lecture 12: Comparison Sorting. Ruth Anderson Winter 2019

a) For the binary tree shown below, what are the preorder, inorder and post order traversals.

About the Final. Saturday, 7-10pm in Science Center 101. Closed book, closed notes. Not on the final: graphics, file I/O, vim, unix

Binary Values. CSE 410 Lecture 02

CSE 332: Data Structures & Parallelism Lecture 3: Priority Queues. Ruth Anderson Winter 2019

9. Heap : Priority Queue

Notes on Project 1. version September 01, 2014

Lecture 6: Analysis of Algorithms (CS )

BST Deletion. First, we need to find the value which is easy because we can just use the method we developed for BST_Search.

Principles of Algorithm Design

Treaps. 1 Binary Search Trees (BSTs) CSE341T/CSE549T 11/05/2014. Lecture 19

22 Elementary Graph Algorithms. There are two standard ways to represent a

Computational Geometry

CMSC 341 Lecture 16/17 Hashing, Parts 1 & 2

Disk Accesses. CS 361, Lecture 25. B-Tree Properties. Outline

Advanced Algorithm Design and Analysis (Lecture 12) SW5 fall 2005 Simonas Šaltenis E1-215b

Computer Science 210 Data Structures Siena College Fall Topic Notes: Priority Queues and Heaps

Hashing. So what we do instead is to store in each slot of the array a linked list of (key, record) - pairs, as in Fig. 1. Figure 1: Chaining

Quiz 1 Practice Problems

22 Elementary Graph Algorithms. There are two standard ways to represent a

Algorithms in Systems Engineering ISE 172. Lecture 5. Dr. Ted Ralphs

Lecture: Analysis of Algorithms (CS )

Readings. Priority Queue ADT. FindMin Problem. Priority Queues & Binary Heaps. List implementation of a Priority Queue

Computational Mathematics with Python

COMP combinational logic 1 Jan. 18, 2016

1 The range query problem

MEMOIZATION, RECURSIVE DATA, AND SETS

Lecture 5. Treaps Find, insert, delete, split, and join in treaps Randomized search trees Randomized search tree time costs

ECE Spring 2018 Problem Set #0 Due: 1/30/18

Algorithms for Minimum Spanning Trees

CS 251, LE 2 Fall MIDTERM 2 Tuesday, November 1, 2016 Version 00 - KEY

Trees & Tree-Based Data Structures. Part 4: Heaps. Definition. Example. Properties. Example Min-Heap. Definition

Friday Four Square! 4:15PM, Outside Gates

CS61B Lecture #21: Tree Searching. Last modified: Wed Oct 12 19:23: CS61B: Lecture #21 1

Short Python function/method descriptions:

CS 331 Midterm Exam 2

There are four numeric types: 1. Integers, represented as a 32 bit (or longer) quantity. Digits sequences (possibly) signed are integer literals:

Decreasing a key FIB-HEAP-DECREASE-KEY(,, ) 3.. NIL. 2. error new key is greater than current key 6. CASCADING-CUT(, )

III Data Structures. Dynamic sets

Last week. Last week. Last week. Today. Binary Trees. CSC148 Intro. to Computer Science. Lecture 8: Binary Trees, BST

Lecture 5: Scheduling and Binary Search Trees

Trees, Part 1: Unbalanced Trees

Review implementation of Stable Matching Survey of common running times. Turn in completed problem sets. Jan 18, 2019 Sprenkle - CSCI211

The priority is indicated by a number, the lower the number - the higher the priority.

CS521 \ Notes for the Final Exam

8. Write an example for expression tree. [A/M 10] (A+B)*((C-D)/(E^F))

CSC148-Section:L0301

Sorted Arrays. Operation Access Search Selection Predecessor Successor Output (print) Insert Delete Extract-Min

Thus, it is reasonable to compare binary search trees and binary heaps as is shown in Table 1.

How many leaves on the decision tree? There are n! leaves, because every permutation appears at least once.

! Tree: set of nodes and directed edges. ! Parent: source node of directed edge. ! Child: terminal node of directed edge

Shadows in the graphics pipeline

Interval Stabbing Problems in Small Integer Ranges

Photon Mapping. Kadi Bouatouch IRISA

DATA STRUCTURE AND ALGORITHM USING PYTHON

Geometric Algorithms. Geometric search: overview. 1D Range Search. 1D Range Search Implementations

Text Input and Conditionals

Physical Design of Digital Integrated Circuits (EN0291 S40) Sherief Reda Division of Engineering, Brown University Fall 2006

PIC 16: Midterm. Part 1. In this part, you should not worry about why your class has the name that it does.

GIS 4653/5653: Spatial Programming and GIS. More Python: Statements, Types, Functions, Modules, Classes

CSE100. Advanced Data Structures. Lecture 8. (Based on Paul Kube course materials)

Computational Optimization ISE 407. Lecture 16. Dr. Ted Ralphs

Solutions to Problem Set 1

n 1 i = n + i = n + f(n 1)

Outline. An Application: A Binary Search Tree. 1 Chapter 7: Trees. favicon. CSI33 Data Structures

DATA STRUCTURES AND ALGORITHMS

Week - 04 Lecture - 01 Merge Sort. (Refer Slide Time: 00:02)

kd-trees Idea: Each level of the tree compares against 1 dimension. Let s us have only two children at each node (instead of 2 d )

26 The closest pair problem

Heaps with merging. We can implement the other priority queue operations in terms of merging!

Outline for Today. How can we speed up operations that work on integer data? A simple data structure for ordered dictionaries.

COMP3121/3821/9101/ s1 Assignment 1

Transcription:

Simulation Algorithms Simulations are immensely useful, both for profit (design validation) and fun (games such as Angry Birds). Simulations can be optimized with the aid of data structures such as heap-backed priority queues, and by sorting algorithms. Most simulations have a discrete clock, which means that the simulation timeline consists of small, equally-sized time quanta, and the universe changes at the beginning of each quanta. For example, a physical simulation of the balls on a billiard table would update the ball positions each 10 milliseconds. As long the time quanta are small, users will not realize the discrete nature of the simulation. Discrete simulations must assume that world changes can be modeled as happening instantaneously, because the changes are computed at the beginning of each quanta. Under this assumption, we can speed up the simulation by figuring out the times when changes happen, instead of simulating every time quantum. Events The circuit simulation model in Problem Set 2 conveniently specified that gate output values change instantaneously (in real life, a gate s output voltage transitions continuously between the minimum voltage and the maximum voltage, over a period of time). This allowed our simulator to work by computing the effects caused by changes in gate output values, instead of simulating every time quantum. From now on, we will call these changes events. For every event (gate output change), we looked at the gates whose inputs are connected to the gate s output and, if the input changes caused a change in the gates output values, we created new events to account for these changes. In order to get the correct results during simulation, the simulator needed to consider the events in chronological order, so it used a priority queue to manage events. Events were added to the queue as they were created, and the simulation code popped the top event (with the minimum step) off the queue at each step. Line-Sweep Algorithms The sweep-line algorithm in Problem Set 3 involves simulating a vertical line that sweeps left-toright through the wire layout, so we can treat it as a simulation problem. This time around, all the events can be computed directly from the input data, before starting the simulation. So we populate a Python list (array) with all the events. 1 class CrossVerifier(object): 2 def _events_from_layer(self, layer): 3 """Populates the sweep line events from the wire layer.""" 4 left_edge = min([wire.x1 for wire in layer.wires.values()]) 5 for wire in layer.wires.values(): 6 if wire.is_horizontal(): 7 self.events.append([left_edge, 0, wire.object_id, add, wire]) 8 else: 9 self.events.append([wire.x1, 1, wire.object_id, query, wire])

The events are sorted according to their time (the x coordinate of the sweep line) in the init method of CrossVerifier, and then compute crossings iterates over the sorted list and processes the events in chronological order (lines 9-10). 1 class CrossVerifier(object): 2 def _compute_crossings(self, count_only): 3 """Implements count_crossings and wire_crossings.""" 4 if count_only: 5 result = 0 6 else: 7 result = self.result_set 8 9 for event in self.events: 10 event_x, event_type, wire = event[0], event[3], event[4] 11 12 if event_type == add : 13 self.index.add(keywirepair(wire.y1, wire)) 14 elif event_type == query : 15 self.trace_sweep_line(event_x) 16 cross_wires = [] 17 for kwp in self.index.list(keywirepairl(wire.y1), 18 KeyWirePairH(wire.y2)): 19 if wire.intersects(kwp.wire): 20 cross_wires.append(kwp.wire) 21 if count_only: 22 result += len(cross_wires) 23 else: 24 for cross_wire in cross_wires: 25 result.add_crossing(wire, cross_wire) 26 27 return result The hacked-together code that ships with Problem Set 3 has two types of events: add and query. First off, all the horizontal wires are added to the range index, keyed according to their y coordinates. Then the sweep line moves across the wire layout from left to right and, every time it hits a vertical wire, a query event occurs. During a query, the range index is asked to provide all the horizontal wires whose y coordinates are between the y coordinates of the vertical wire s endpoints. This list of wires may contain false positives wires that are completely to the left or completely to the right of the vertical wire, but it will definitely contain all the wires that intersect the vertical wire. Convince yourself that none of the other horizontal wires could possibly intersect the vertical wire. Comparison Model I We have learned in lecture that using the comparison model means we can t do searching in better than Ω(log n) time, and we can t sort faster than Ω(N log N). The advantage of the comparison model is that it can encompass arbitrarily complicated objects, as long as we can define an ordering relationship on them.

The events in Problem Set 3 are Python lists (arrays), because Python conveniently implements an ordering relationship on list, based on lexicographical ordering 1. We ll call the lists used to represent events vectors, to distinguish them from the array that holds all the events. The first element in an event s vector (main comparison criterion) is the event s x coordinate, so that events are processed left-to-right, modeling a vertical line that sweeps across the entire plane left-to-right. The next two elements in an event s vector are used to break ties among events that happen at the same sweep line position. They are 1. a small integer representing the event type s priority; this ensures that add events at an x coordinate are processed before query events at that coordinate 2. a wire ID, which is a unique number; this ensures that the lexicographical comparison algorithm will never go past this element, so it will not attempt to compare Wire instance Last, the inefficient sweep-line algorithm supplied in Problem Set 3 wants to process all the add events before any query event, so it sets the x coordinate for all the add events to be the minimum among the x coordinates of all wire endpoints. Together with setting the priority of add events set to 0, this ensures that add events come ahead of query events during comparisons. 1 class CrossVerifier(object): 2 def _compute_crossings(self, count_only): 3 """Implements count_crossings and wire_crossings.""" 4 if count_only: 5 result = 0 6 else: 7 result = self.result_set 8 9 for event in self.events: 10 event_x, event_type, wire = event[0], event[3], event[4] 11 12 if event_type == add : 13 self.index.add(keywirepair(wire.y1, wire)) 14 elif event_type == query : 15 self.trace_sweep_line(event_x) 16 cross_wires = [] 17 for kwp in self.index.list(keywirepairl(wire.y1), 18 KeyWirePairH(wire.y2)): 19 if wire.intersects(kwp.wire): 20 cross_wires.append(kwp.wire) 21 if count_only: 22 result += len(cross_wires) 23 else: 24 for cross_wire in cross_wires: 25 result.add_crossing(wire, cross_wire) 26 27 return result 1 the same ordering used for words in a dictionary, e.g. Alyssa < Andrew

Range Queries The wire crossing algorithm supplied in Problem Set 3 adds all horizontal wires to a range index, which is later queried to find all the wires that can potentially intersect a vertical wire. Range Index API The range index concept is outlined in Problem 1 of Problem Set 3. 1 class RangeIndex(object): 2 """Array-based range index implementation.""" 3 4 def init (self): 5 """Initially empty range index.""" 6 self.data = [] 7 8 def add(self, key): 9 """Inserts a key in the range index.""" 10 if key is None: 11 raise ValueError( Cannot insert nil in the index ) 12 self.data.append(key) 13 14 def remove(self, key): 15 """Removes a key from the range index.""" 16 self.data.remove(key) 17 18 def list(self, first_key, last_key): 19 """List of values for the keys that fall within [first_key, last_key].""" 20 return [key for key in self.data if first_key <= key <= last_key] 21 22 def count(self, first_key, last_key): 23 """Number of keys that fall within [first_key, last_key].""" 24 result = 0 25 for key in self.data: 26 if first_key <= key <= last_key: 27 result += 1 28 return result A range index supports ADD(x) and REMOVE(x) updates to build up a set of keys, and offers range queries over the keys. COUNT(l, h) returns the number of keys in the range index that belong to the [l, h] range (l key h) and LIST(l, h) returns a list of the above-mentioned keys. The code supplied in circuit2.py is optimized for size, not for speed, to convey the concept of a range index. Convince yourself that ADD runs in O(1) time, but REMOVE, COUNT runs in O(N) time, and LIST run in O(N + K) time, where K is the number of keys in the list returned by LIST. A first cut at optimizing range indexes would use a sorted array. 1 class BlitRangeIndex(object): 2 """Sorted array-based range index implementation.""" 3

4 def init (self): 5 """Initially empty range index.""" 6 self.data = [] 7 8 def add(self, key): 9 """Inserts a key in the range index.""" 10 if key is None: 11 raise ValueError( Cannot insert None in the index ) 12 self.data.insert(self._binary_search(key), key) 13 14 def remove(self, key): 15 """Removes a key from the range index.""" 16 index = self._binary_search(key) 17 if index < len(self.data) and self.data[index] == key: 18 self.data.pop(index) 19 20 def list(self, low_key, high_key): 21 """List of values for the keys that fall within [low_key, high_key].""" 22 low_index = self._binary_search(low_key) 23 high_index = self._binary_search(high_key) 24 return self.data[low_index:high_index] 25 26 def count(self, low_key, high_key): 27 """Number of keys that fall within [low_key, high_key].""" 28 low_index = self._binary_search(low_key) 29 high_index = self._binary_search(high_key) 30 return high_index - low_index 31 32 def _binary_search(self, key): 33 """Binary search for the given key in the sorted array.""" 34 low, high = 0, len(self.data) - 1 35 while low <= high: 36 mid = (low + high) // 2 37 mid_key = self.data[mid] 38 if key < mid_key: 39 high = mid - 1 40 elif key > mid_key: 41 low = mid + 1 42 else: 43 return mid 44 return high + 1 This implementation is a bit more complicated, but runs a lot faster. COUNT runs in O(log N) time, and LIST runs in O(log(N) + K) time, but in exchange ADD runs in O(N) time. REMOVE still runs in O(N) time, like before. In a query-heavy workload, BlitRangeIndex will vastly outperform RangeIndex. If there are a lot of updates, the performance won t be too bad, because the O(N) operations in ADD and REMOVE, as well as the O(K) operation in LIST, are built-in Python functions, which are most likely implemented in C, and therefore have very small constant factors. For many practical cases, this implementation will do very well. However, on really large data sets, this code will be out-performed by an implementation of an algorithm with better

asymptotic performance. Comparison Model II We can use the comparison model to introduce an ordering relationship between all the wires in the range index horizontal wires should be ordered by their y coordinate. The first instinct when implementing this ordering relationship would be to augment the Wire class with the right magic methods, so Wire instances can be compared using the standard Python operators <, <=, > and >=. However, this approach is dirty What if we later want to order vertical wires by their x coordinate? How about ordering all the wires by their names? Moreover, the approach falls apart when it comes time to do a range query how would we create keys representing the y coordinates of a vertical wire s endpoints? Fake Wire instances come to mind, but that s also quite hackish. Instead, we create a new kind of objects, KeyWirePair, which are used as the range index keys. 1 class KeyWirePair(object): 2 """Wraps a wire and the key representing it in the range index. 3 4 Once created, a key-wire pair is immutable.""" 5 6 def init (self, key, wire): 7 """Creates a new key for insertion in the range index.""" 8 self.key = key 9 self.wire = wire 10 self.wire_id = wire.object_id A KeyWirePair instance that is inserted into the range index has its key set to the wires y coordinate. Comparisons use the key field as the primary criterion, and then use wire id (a wire s unique ID) to break ties. Two keys with the same wire id represent the same wire, so they are equal. It doesn t make sense to insert the same wire into the range index twice, so the data structure used to implement the range index doesn t have to deal with equal keys. 1 class KeyWirePair(object): 2 def lt (self, other): 3 return (self.key < other.key or 4 (self.key == other.key and self.wire_id < other.wire_id)) 5 6 def le (self, other): 7 return (self.key < other.key or 8 (self.key == other.key and self.wire_id <= other.wire_id)) 9 10 def gt (self, other): 11 return (self.key > other.key or 12 (self.key == other.key and self.wire_id > other.wire_id)) 13 14 def ge (self, other): 15 return (self.key > other.key or 16 (self.key == other.key and self.wire_id >= other.wire_id))

17 18 def eq (self, other): 19 return self.key == other.key and self.wire_id == other.wire_id 20 21 def ne (self, other): 22 return self.key == other.key and self.wire_id == other.wire_id Note the special method names (e.g., le means less than or equal to and is used to implement the <= operator). KeyWirePair instances used for range queries have the wire field set to None. To facilitate range queries, we subclass KeyWirePair with KeyWirePairL and KeyWirePairH, which are meant to be used as the low and high keys in range queries. 1 class KeyWirePairL(KeyWirePair): 2 def init (self, key): 3 self.key = key 4 self.wire = None 5 self.wire_id = -1000000000 6 7 class KeyWirePairH(KeyWirePair): 8 def init (self, key): 9 self.key = key 10 self.wire = None 11 self.wire_id = 1000000000 Asides from setting wire to None, the special classes set the wire id field to special values that are guaranteed to be smaller / greater than all the IDs of real wires. This means that a KeyWirePairL with a certain key (y coordinate) will be considered lower than all the keys for wires with the same y coordinate and, conversely, a KeyWirePairH will be higher than the keys of wires with the same y coordinate. This implementation trick eliminates some boundary cases for range queries, because the keys in the index are guaranteed to not be equal to the keys used in queries. The need of being able to bound wire IDs in KeyWirePairL and KeyWirePairH pushed us away from Python s built-in id() function, which provides unique IDs for all Python objects. Instead, the verifier implements its own ID scheme for Wire instances. 1 class Wire(object): 2 def init (self, name, x1, y1, x2, y2): 3 # Normalize the coordinates. 4 if x1 > x2: 5 x1, x2 = x2, x1 6 if y1 > y2: 7 y1, y2 = y2, y1 8 9 self.name = name 10 self.x1, self.y1 = x1, y1 11 self.x2, self.y2 = x2, y2 12 self.object_id = Wire.next_object_id() 13 14 # Next number handed out by Wire.next_object_id()

15 _next_id = 0 16 17 @staticmethod 18 def next_object_id(): 19 """Returns a unique numerical ID to be used as a Wire s object_id.""" 20 id = Wire._next_id 21 Wire._next_id += 1 22 return id Wire IDs are consecutive non-negative integers, so it s reasonable to assume they will be bounded by 1,000,000,000 (a billion), because storing a billion wires would require a lot of RAM. LIST Range Queries in BSTs Given a binary search tree (BST), we want to implement LIST(tree, l, h) which produces a list of all the keys between l and h (l key h), in O(log(N) + K) time, where N is the number of keys in the tree, and K is the number of keys output by LIST. We will convince ourselves that the pseudo-code below will do the trick. LIST(tree, l, h) 1 lca = LCA(tree, l, h) 2 result = [] 3 NODE-LIST(lca, l, h, result) 4 return result First off we identify the smallest subtree that contains all the keys between l and h. Intuitively, LCA computes the root of that subtree. Formally, LCA computes the lowest-common ancestor of nodes with keys l and h. If l and h do not exist in the tree, LCA returns the lowest-common ancestor of the two nodes that would be created by inserting l and h. LCA(tree, l, h) 1 node = tree.root 2 until node == NIL or (l node.key and h node.key) 3 if l < node.key 4 node = node.left 5 else 6 node = node.right 7 return node Once the subtree s root is found by LCA, NODE-LIST performs an in-order traversal of the subtree, but prunes the subtrees that are guaranteed to contain keys outside the [l, h] range.

NODE-LIST(node, l, h, result) 1 if node == NIL 2 return 3 if node.key l 4 NODE-LIST(node.left, l, h, result) 5 if l node.key and node.key h 6 ADD-KEY(result, node.key) 7 if node.key h 8 NODE-LIST(node.right, l, h, result)