From d6af65d4178609cb5dbf0edc6d431777d2a48838 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 10:14:10 +0100 Subject: [PATCH 1/8] [#76] Add property and setter methods for unavailability --- docs/tutorial/index.rst | 26 +++++++++++++------------- src/conference_scheduler/resources.py | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index ec52e5b..e1984f7 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -84,12 +84,12 @@ Further to this we have a couple of other constraints: - The speaker for :code:`Talk 1` is also the person delivering :code:`Workshop 1`:: - >>> events[0].unavailability.append(events[6]) + >>> events[0].add_unavailability(events[6]) - Also, the person running :code:`Workshop 2` is the person hosting the :code:`Boardgames`:: - >>> events[13].unavailability.append(events[-1]) + >>> events[13].add_unavailability(events[-1]) Note that we haven't indicated the workshops cannot happen in the talk slots but this will automatically be taken care of because of the duration of the @@ -182,7 +182,7 @@ informs us of a particular new constraints. For example, the speaker for We can enter this new constraint:: - >>> events[10].unavailability.extend(slots[9:]) + >>> events[10].add_unavailability(slots[9:]) We can now solve the problem one more time from scratch just as before:: @@ -345,17 +345,17 @@ As you can see, we have set all unavailabilities to be empty however has informed us that they are not present on the first day. We can include these constraints:: - >>> events[0].unavailability.append(chair_slots[4]) - >>> events[1].unavailability.append(chair_slots[4]) - >>> events[2].unavailability.extend(chair_slots[4:]) - >>> events[3].unavailability.extend(chair_slots[4:]) + >>> events[0].add_unavailability(chair_slots[4]) + >>> events[1].add_unavailability(chair_slots[4]) + >>> events[2].add_unavailability(chair_slots[4:]) + >>> events[3].add_unavailability(chair_slots[4:]) Finally, each chair cannot chair more than one session at a time:: - >>> events[0].unavailability.append(events[1]) - >>> events[2].unavailability.append(events[3]) - >>> events[4].unavailability.append(events[5]) + >>> events[0].add_unavailability(events[1]) + >>> events[2].add_unavailability(events[3]) + >>> events[4].add_unavailability(events[5]) Now let us get the chair schedule:: @@ -367,9 +367,9 @@ Now let us get the chair schedule:: Chair A-2 chairing 15-Sep-2016 09:30 in Big Chair B-1 chairing 15-Sep-2016 09:30 in Small Chair B-2 chairing 15-Sep-2016 12:30 in Small - Chair C-1 chairing 15-Sep-2016 12:30 in Big - Chair A-1 chairing 16-Sep-2016 12:30 in Small - Chair D-2 chairing 16-Sep-2016 12:30 in Big + Chair D-2 chairing 15-Sep-2016 12:30 in Big + Chair A-1 chairing 16-Sep-2016 12:30 in Big + Chair C-1 chairing 16-Sep-2016 12:30 in Small Validating a schedule --------------------- diff --git a/src/conference_scheduler/resources.py b/src/conference_scheduler/resources.py index d541d76..040640e 100644 --- a/src/conference_scheduler/resources.py +++ b/src/conference_scheduler/resources.py @@ -21,7 +21,7 @@ def __init__(self, name, duration, demand, tags=None, unavailability=None): self.tags = tags if unavailability is None: unavailability = [] - self.unavailability = unavailability + self._unavailability = unavailability def __repr__(self): return ( @@ -48,6 +48,19 @@ def __ne__(self, other): def __hash__(self): return hash(repr(self)) + @property + def unavailability(self): + return tuple(self._unavailability) + + def add_unavailability(self, object): + try: + self._unavailability.extend(object) + except TypeError: + self._unavailability.append(object) + + def remove_unavailability(self, object): + self._unavailability.remove(object) + class ScheduledItem(NamedTuple): event: Event From 04dae5e0836e94c21021acffa09815ddf13e7445 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 10:18:02 +0100 Subject: [PATCH 2/8] [#76] Amend resources test for unavailability property --- tests/test_resources.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_resources.py b/tests/test_resources.py index e0aab08..e4f497e 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -12,13 +12,13 @@ def test_can_construct_event(): assert isinstance(e, Event) assert e.name == 'example' assert e.tags == ['beginner', 'python'] - assert e.unavailability == [] + assert e.unavailability == () def test_optional_args_to_event_are_defaulted(): e = Event(name='example', duration=60, demand=100) assert e.tags == [] - assert e.unavailability == [] + assert e.unavailability == () def test_optional_args_are_safely_mutable(): @@ -38,3 +38,4 @@ def test_optional_args_are_safely_mutable(): def test_event_is_hashable(): e = Event(name='example', duration=60, demand=100) events = set([e]) + assert len(events) == 1 From fcba64223eda79625a5a63550a9415e49810b41e Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 10:49:31 +0100 Subject: [PATCH 3/8] [#76] Add tags property to Event class --- src/conference_scheduler/resources.py | 21 ++++++++- tests/test_resources.py | 64 ++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/conference_scheduler/resources.py b/src/conference_scheduler/resources.py index 040640e..e55f35f 100644 --- a/src/conference_scheduler/resources.py +++ b/src/conference_scheduler/resources.py @@ -18,7 +18,7 @@ def __init__(self, name, duration, demand, tags=None, unavailability=None): self.demand = demand if tags is None: tags = [] - self.tags = tags + self._tags = tags if unavailability is None: unavailability = [] self._unavailability = unavailability @@ -61,6 +61,25 @@ def add_unavailability(self, object): def remove_unavailability(self, object): self._unavailability.remove(object) + def clear_unavailability(self): + del self._unavailability[:] + + @property + def tags(self): + return tuple(self._tags) + + def add_tag(self, tag): + self._tags.append(tag) + + def add_tags(self, tags): + self._tags.extend(tags) + + def remove_tag(self, tag): + self._tags.remove(tag) + + def clear_tags(self): + del self._tags[:] + class ScheduledItem(NamedTuple): event: Event diff --git a/tests/test_resources.py b/tests/test_resources.py index e4f497e..48d1dd5 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -11,13 +11,13 @@ def test_can_construct_event(): ) assert isinstance(e, Event) assert e.name == 'example' - assert e.tags == ['beginner', 'python'] + assert e.tags == ('beginner', 'python') assert e.unavailability == () def test_optional_args_to_event_are_defaulted(): e = Event(name='example', duration=60, demand=100) - assert e.tags == [] + assert e.tags == () assert e.unavailability == () @@ -25,17 +25,69 @@ def test_optional_args_are_safely_mutable(): # Construct an instance of `Event` with the optional arguments, # omitted, then assign it a tag e = Event(name='example', duration=60, demand=100) - assert e.tags == [] - e.tags.append('intermediate') - assert e.tags == ['intermediate'] + assert e.tags == () + e.add_tag('intermediate') + assert e.tags == ('intermediate', ) # Now create a second instance of `Event`, and check we haven't # polluted the default arguments. f = Event(name='another example', duration=30, demand=50) - assert f.tags == [] + assert f.tags == () def test_event_is_hashable(): e = Event(name='example', duration=60, demand=100) events = set([e]) assert len(events) == 1 + + +def test_add_unavailability_append(): + e = Event(name='example', duration=60, demand=100) + e.add_unavailability(2) + assert e.unavailability == (2, ) + + +def test_add_unavailability_extend(): + e = Event(name='example', duration=60, demand=100) + e.add_unavailability([2, 3, 4]) + assert e.unavailability == (2, 3, 4) + + +def test_remove_unavailability(): + e = Event(name='example', duration=60, demand=100) + e.add_unavailability([2, 3, 4]) + e.remove_unavailability(3) + assert e.unavailability == (2, 4) + + +def test_clear_unavailability(): + e = Event(name='example', duration=60, demand=100) + e.add_unavailability([2, 3, 4]) + e.clear_unavailability() + assert e.unavailability == () + + +def test_add_tag(): + e = Event(name='example', duration=60, demand=100) + e.add_tag('test') + assert e.tags == ('test', ) + + +def test_add_tags(): + e = Event(name='example', duration=60, demand=100) + e.add_tags(['test1', 'test2', 'test3']) + assert e.tags == ('test1', 'test2', 'test3') + + +def test_remove_tag(): + e = Event(name='example', duration=60, demand=100) + e.add_tags(['test1', 'test2', 'test3']) + e.remove_tag('test2') + assert e.tags == ('test1', 'test3') + + +def test_clear_tags(): + e = Event(name='example', duration=60, demand=100) + e.add_tags(['test1', 'test2', 'test3']) + e.clear_tags() + assert e.tags == () From 9efc5d9916c758767c3a7a84fa415bdffefdb0b8 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 11:00:53 +0100 Subject: [PATCH 4/8] [#76] Amend tag constraint to check for empty tuple rather than list --- src/conference_scheduler/lp_problem/constraints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conference_scheduler/lp_problem/constraints.py b/src/conference_scheduler/lp_problem/constraints.py index 3db0dd6..e82ed76 100644 --- a/src/conference_scheduler/lp_problem/constraints.py +++ b/src/conference_scheduler/lp_problem/constraints.py @@ -45,7 +45,7 @@ def _events_in_session_share_a_tag(events, slots, X, summation_type=None): for session in session_indices: slots = lpu._slots_in_session(session, session_array) for slot, event in it.product(slots, event_indices): - if events[event].tags != []: + if events[event].tags != (): other_events = lpu._events_with_diff_tag(event, tag_array) for other_slot, other_event in it.product(slots, other_events): if other_slot != slot and other_event != event: From 827a1e7f8612b31e01fddfcff2e5ccb3de3fe518 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 11:01:48 +0100 Subject: [PATCH 5/8] [#76] Use length of event.tags in tag constraint --- src/conference_scheduler/lp_problem/constraints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conference_scheduler/lp_problem/constraints.py b/src/conference_scheduler/lp_problem/constraints.py index e82ed76..09dd0e8 100644 --- a/src/conference_scheduler/lp_problem/constraints.py +++ b/src/conference_scheduler/lp_problem/constraints.py @@ -45,7 +45,7 @@ def _events_in_session_share_a_tag(events, slots, X, summation_type=None): for session in session_indices: slots = lpu._slots_in_session(session, session_array) for slot, event in it.product(slots, event_indices): - if events[event].tags != (): + if len(events[event].tags) > 0: other_events = lpu._events_with_diff_tag(event, tag_array) for other_slot, other_event in it.product(slots, other_events): if other_slot != slot and other_event != event: From f6271a5ae28c659eccace0918aca3c4f448bf838 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 12:41:34 +0100 Subject: [PATCH 6/8] [#76] Make tags and unavailability methods consistent by using *args --- src/conference_scheduler/resources.py | 16 ++++++---------- tests/test_resources.py | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/conference_scheduler/resources.py b/src/conference_scheduler/resources.py index e55f35f..e56ce5c 100644 --- a/src/conference_scheduler/resources.py +++ b/src/conference_scheduler/resources.py @@ -52,11 +52,9 @@ def __hash__(self): def unavailability(self): return tuple(self._unavailability) - def add_unavailability(self, object): - try: - self._unavailability.extend(object) - except TypeError: - self._unavailability.append(object) + def add_unavailability(self, *args): + for arg in args: + self._unavailability.append(arg) def remove_unavailability(self, object): self._unavailability.remove(object) @@ -68,11 +66,9 @@ def clear_unavailability(self): def tags(self): return tuple(self._tags) - def add_tag(self, tag): - self._tags.append(tag) - - def add_tags(self, tags): - self._tags.extend(tags) + def add_tags(self, *args): + for arg in args: + self._tags.append(arg) def remove_tag(self, tag): self._tags.remove(tag) diff --git a/tests/test_resources.py b/tests/test_resources.py index 48d1dd5..8a1ea03 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -26,7 +26,7 @@ def test_optional_args_are_safely_mutable(): # omitted, then assign it a tag e = Event(name='example', duration=60, demand=100) assert e.tags == () - e.add_tag('intermediate') + e.add_tags('intermediate') assert e.tags == ('intermediate', ) # Now create a second instance of `Event`, and check we haven't @@ -41,53 +41,53 @@ def test_event_is_hashable(): assert len(events) == 1 -def test_add_unavailability_append(): +def test_add_single_unavailability(): e = Event(name='example', duration=60, demand=100) e.add_unavailability(2) assert e.unavailability == (2, ) -def test_add_unavailability_extend(): +def test_add_multiple_unavailability(): e = Event(name='example', duration=60, demand=100) - e.add_unavailability([2, 3, 4]) + e.add_unavailability(2, 3, 4) assert e.unavailability == (2, 3, 4) def test_remove_unavailability(): e = Event(name='example', duration=60, demand=100) - e.add_unavailability([2, 3, 4]) + e.add_unavailability(2, 3, 4) e.remove_unavailability(3) assert e.unavailability == (2, 4) def test_clear_unavailability(): e = Event(name='example', duration=60, demand=100) - e.add_unavailability([2, 3, 4]) + e.add_unavailability(2, 3, 4) e.clear_unavailability() assert e.unavailability == () -def test_add_tag(): +def test_add_single_tag(): e = Event(name='example', duration=60, demand=100) - e.add_tag('test') + e.add_tags('test') assert e.tags == ('test', ) -def test_add_tags(): +def test_add_multiple_tags(): e = Event(name='example', duration=60, demand=100) - e.add_tags(['test1', 'test2', 'test3']) + e.add_tags('test1', 'test2', 'test3') assert e.tags == ('test1', 'test2', 'test3') def test_remove_tag(): e = Event(name='example', duration=60, demand=100) - e.add_tags(['test1', 'test2', 'test3']) + e.add_tags('test1', 'test2', 'test3') e.remove_tag('test2') assert e.tags == ('test1', 'test3') def test_clear_tags(): e = Event(name='example', duration=60, demand=100) - e.add_tags(['test1', 'test2', 'test3']) + e.add_tags('test1', 'test2', 'test3') e.clear_tags() assert e.tags == () From d1326e836d0792c1ff8c499aed05eb7938cc0c7f Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 12:58:33 +0100 Subject: [PATCH 7/8] [#76] Add args unpacking to tutorial --- docs/tutorial/index.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index e1984f7..8c8dcc5 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -124,7 +124,6 @@ event:: Workshop 1 at 16-Sep-2016 13:00 in Small City tour at 16-Sep-2016 13:00 in Outside - We see that all the events are scheduled in appropriate rooms (as indicated by the unavailability attribute for the events). Also we have that :code:`Talk 1` doesn't clash with :code:`Workshop 1`. @@ -178,11 +177,11 @@ Coping with new information This is fantastic! Our schedule has now been published and everyone is excited about the conference. However, as can often happen, one of the speakers now informs us of a particular new constraints. For example, the speaker for -:code:`Talk 11` is unable to speak on the second day. +:code:`Talk 11` is unable to speak on the first day. We can enter this new constraint:: - >>> events[10].add_unavailability(slots[9:]) + >>> events[10].add_unavailability(*slots[9:]) We can now solve the problem one more time from scratch just as before:: @@ -238,6 +237,7 @@ old schedule:: City tour at 16-Sep-2016 13:00 in Outside + Spotting the Changes -------------------- It can be a little difficult to spot what has changed when we compute a new schedule and so @@ -345,10 +345,10 @@ As you can see, we have set all unavailabilities to be empty however has informed us that they are not present on the first day. We can include these constraints:: - >>> events[0].add_unavailability(chair_slots[4]) - >>> events[1].add_unavailability(chair_slots[4]) - >>> events[2].add_unavailability(chair_slots[4:]) - >>> events[3].add_unavailability(chair_slots[4:]) + >>> events[0].add_unavailability(*chair_slots[4]) + >>> events[1].add_unavailability(*chair_slots[4]) + >>> events[2].add_unavailability(*chair_slots[4:]) + >>> events[3].add_unavailability(*chair_slots[4:]) Finally, each chair cannot chair more than one session at a time:: @@ -371,6 +371,7 @@ Now let us get the chair schedule:: Chair A-1 chairing 16-Sep-2016 12:30 in Big Chair C-1 chairing 16-Sep-2016 12:30 in Small + Validating a schedule --------------------- From 33ecc7b732b1692fb08a6835d627676bf98a2a51 Mon Sep 17 00:00:00 2001 From: Owen Campbell Date: Fri, 12 May 2017 16:43:32 +0100 Subject: [PATCH 8/8] [#76] Remove * operator from single args --- docs/tutorial/index.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 8c8dcc5..d3db103 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -345,8 +345,8 @@ As you can see, we have set all unavailabilities to be empty however has informed us that they are not present on the first day. We can include these constraints:: - >>> events[0].add_unavailability(*chair_slots[4]) - >>> events[1].add_unavailability(*chair_slots[4]) + >>> events[0].add_unavailability(chair_slots[4]) + >>> events[1].add_unavailability(chair_slots[4]) >>> events[2].add_unavailability(*chair_slots[4:]) >>> events[3].add_unavailability(*chair_slots[4:]) @@ -367,9 +367,9 @@ Now let us get the chair schedule:: Chair A-2 chairing 15-Sep-2016 09:30 in Big Chair B-1 chairing 15-Sep-2016 09:30 in Small Chair B-2 chairing 15-Sep-2016 12:30 in Small - Chair D-2 chairing 15-Sep-2016 12:30 in Big - Chair A-1 chairing 16-Sep-2016 12:30 in Big - Chair C-1 chairing 16-Sep-2016 12:30 in Small + Chair C-1 chairing 15-Sep-2016 12:30 in Big + Chair A-1 chairing 16-Sep-2016 12:30 in Small + Chair D-2 chairing 16-Sep-2016 12:30 in Big Validating a schedule