Inheritance of OptaPy planning_entitiy classes? - python

I'm working on a schedule optimization solution with Optapy.
I'm trying like to make a chained planned variable, which should include multiple types of actual entities in the chain. Something like this:
#planning_entity
class Slot:
""" Schedule slot """
def __init__(self):
self.prev_slot = None
#planning_variable(Slot, value_range_provider_refs=['slot_range'],
graph_type=PlanningVariableGraphType.CHAINED)
def get_previous_slot(self):
return self.prev_slot
def set_previous_slot(self, previous_slot: 'Slot'):
self.prev_slot = previous_slot
#planning_entity
class Task(Slot):
""" Allocation to task """
...
#planning_entity
class LunchBreak(Slot):
""" Lunch break """
...
This doesn't work by various reasons. If I don't add any planning variable to derived classes, it fails saying I have to, but if I add them - I'm getting java exceptions like 'assignment error' or similar.
Is it actually possible to inherit one #planning_entity class from another in Optapy?

The issue is in this code:
#planning_entity
class Slot:
""" Schedule slot """
def __init__(self):
self.prev_slot = None
#planning_variable(Slot, value_range_provider_refs=['slot_range'],
graph_type=PlanningVariableGraphType.CHAINED)
def get_previous_slot(self):
return self.prev_slot
def set_previous_slot(self, previous_slot: 'Slot'):
self.prev_slot = previous_slot
Due to Python semantics, the class Slot is not available during it own definition. However, chained models / subclasses are possible in optapy. There is a bug in the current version of optapy (https://github.com/optapy/optapy/issues/101) that throws a ClassCastException if the #value_range_provider type does not EXACTLY match the #planning_variable type. This can be work around by using the most derived super class (which in this case, is Slot) in the #planning_solution. Additionally, there seem to be a problem with multiple #planning_entity classes in chained models that I'll investigate. To accomplish having multiple subclasses of planning entities (without new #planning_variables), the following code will work:
import optapy
from optapy.score import HardSoftScore
from optapy.types import PlanningVariableGraphType
#optapy.problem_fact
class Base:
pass
#optapy.planning_entity
class Slot(Base):
def __init__(self, value=None):
self.value = value
#optapy.planning_variable(Base, value_range_provider_refs=['employee_range', 'task_range', 'lunch_break_range'],
graph_type=PlanningVariableGraphType.CHAINED)
def get_value(self):
return self.value
def set_value(self, value):
self.value = value
#optapy.problem_fact
class Employee(Base): # chained models need an anchor
def __init__(self, code):
self.code = code
#optapy.planning_entity
class Task(Slot):
def __init__(self, code, value=None):
self.code = code
self.value = value
#optapy.planning_entity
class LunchBreak(Slot):
def __init__(self, code, value=None):
self.code = code
self.value = value
#optapy.planning_solution
class Solution:
def __init__(self, employees, tasks, lunch_breaks, score=None):
self.employees = employees
self.tasks = tasks
self.lunch_breaks = lunch_breaks
self.score = score
#optapy.problem_fact_collection_property(Slot)
#optapy.value_range_provider('employee_range')
def get_employees(self):
return self.employees
#optapy.planning_entity_collection_property(Slot)
#optapy.value_range_provider('task_range')
def get_tasks(self):
return self.tasks
#optapy.planning_entity_collection_property(Slot)
#optapy.value_range_provider('lunch_break_range')
def get_lunch_breaks(self):
return self.lunch_breaks
#optapy.planning_score(HardSoftScore)
def get_score(self):
return self.score
def set_score(self, score):
self.score = score
def build_problem():
employees = [
Employee('Amy'),
Employee('Beth')
]
tasks = [
Task('T1'),
Task('T2'),
Task('T3')
]
lunch_breaks = [
LunchBreak('L1'),
LunchBreak('L2')
]
return Solution(employees, tasks, lunch_breaks)
And to create the solver:
import optapy
import optapy.config
from optapy.types import Duration
from domain import Solution, Slot, build_problem
from constraints import define_constraints
solver_config = (optapy.config.solver.SolverConfig()
.withSolutionClass(Solution)
.withEntityClasses(Slot)
.withConstraintProviderClass(define_constraints)
.withTerminationSpentLimit(Duration.ofSeconds(10))
)
solver_factory = optapy.solver_factory_create(solver_config)
solver = solver_factory.buildSolver()
solver.solve(build_problem())

Related

Observer pattern updating after unsubscribing

I am currently trying to implement the observer design pattern on a List class. The list class is the subject, and a calculate class is the observer. The calculate class needs to observe the list class, and calculate the sum of the values in the list in the list class. However, when the observer unsubscribes from the List, it is still getting the updates. And if the observer subscribes to a new list, the list before unsubscribing is still showing. Does anyone have any tips to why this happens?
from abc import ABCMeta, abstractmethod
class Observer(metaclass = ABCMeta):
#abstractmethod
def update(self, data):
pass
class Subject(metaclass = ABCMeta):
#abstractmethod
def subscribe(self, observer):
pass
#abstractmethod
def unsubscribe(self, observer):
pass
#abstractmethod
def notify(self):
pass
class List(Subject):
def __init__(self):
self.__list = []
self._observers = set()
def add(self, data):
self.__list.append(data)
self.notify()
def subscribe(self, observer):
self._observers.add(observer)
def unsubscribe(self, observer):
self._observers.discard(observer)
def notify(self):
for obs in self._observers:
obs.update(self.__list)
class Calculate(Observer):
def __init__(self, lst):
self.__data = []
self._list = lst
self._list.subscribe(self)
def update(self, data):
self.__data = data
def calculate(self):
total = 0
for item in self.__data:
total += item
return total
def remove(self):
self._list.unsubscribe(self)
def attach(self, lst):
self._list.subscribe(self)
So, this is my test and the output i get:
first_list = List()
first_list.add(1)
list_observer = Calculate(first_list)
first_list.add(5)
list_observer.calculate()
This returns 6, which is correct. However, when i do this:
list_observer.remove()
first_list.add(5)
list_observer.calculate()
I get 11. Which in sense, is the correct calculation, but since the observer unsubscribed, why is it still getting the updates? I also tried subscribing to a new list, but the old data is still in there.
This has to do with the type of data you pass. The list of data is mutable. I can't explain it that well, but this answer is what you are looking for.
How do I pass a variable by reference?
You can fix it by doing this, you take a copy of the original object:
def update(self, data):
self.__data = data.copy()

How to access a subclass method from parent method?

I’m trying to call a method found within a subclass from the class it inherits from.
class Account:
def __init__(self, full_name):
self.full_name = full_name
class Transactions(Account):
def __init__(self, full_name, amount=0):
super().__init__(full_name)
self._transactions = []
def add_transaction(self, amount):
if not isinstance(amount, int):
return ValueError('Please use an int.')
self._transactions.append(amount)
acc_0 = Account('Forest Whitaker')
I want to call the function ‘add_transaction()’ using acc_0’s info. Not sure if I’m overthinking but how would I go about this?
sidenote: if anyone is familiar with rbx.lua, in this situation I’d be trying to do something like this: acc_0.Transactions.add_transaction(50)
Since you are instantiating an Account, you can't access any transactions, because that class doesn't implement that functionality. You would need to instantiate the subclass Transactions, eg:
acc_0 = Transactions('Forest Whitaker', 9)
There are actually several problems with your code above, but the subclassing isn't really right for this job anyway.. it's a classic "is a" vs "has a" object oriented problem: Do transactions HAVE an account, not really.. Is transactions a type of account?? No, not right either. But, does an account HAVE transactions? Yes, it does. So an instance of Transactions should be a member of Account. So:
class Account:
def __init__(self, full_name):
self.full_name = full_name
self.transactions = Transactions()
class Transactions:
def __init__(self):
super().__init__()
self._transactions = []
def add_transaction(self, amount):
if not isinstance(amount, int):
return ValueError('Please use an int.')
self._transactions.append(amount)
acc_0 = Account('Forest Whitaker')
acc_0.transactions.add_transaction(9)
The inheritance hierarchy is wrong looking at what you are trying to achieve. That should be other way around.
class Transactions:
def __init__(self):
self._transactions = []
def add_transaction(self, amount):
if not isinstance(amount, int):
return ValueError('Please use an int.')
self._transactions.append(amount)
class Account(Transactions):
def __init__(self, full_name):
super().__init__()
self.full_name = full_name
acc_0 = Account('Forest Whitaker')
# now you can call
acc_0.add_transaction(10)

Access attribute of one instance from another instance defined in that instance

Sorry for the confusing question.
Say there are two instances of two different classes (e.g. 'big_instance' and 'little_instance').
The little_instance is defined as an attribute of the big instance.
How would a method in the little class access an attribute of the big instance.
An example is below.
The line 'return parent.attribute1' is basically pseudo code. How would this line be written properly?
class BigClass:
def __init__(self, att):
self.attribute1 = att
self.little_instance = LittleClass()
class LittleClass:
def parents_att(self):
return parent.attribute1
big_instance = BigClass(1)
print(big_instance.little_instance.parents_att())
ah yes I got it. Read the comments for explanation.
The test code at the end shows that it works even after attribute1 changes :)
class BigClass:
def __init__(self, att):
self.attribute1 = att
# pass in self
self.little_instance = LittleClass(self)
class LittleClass:
def __init__(self, the_big_class):
# the big class is held in an instance var
self.the_big_class = the_big_class
def parents_att(self):
# the instance var is used to reference the attribute
return self.the_big_class.attribute1
big_instance = BigClass(1)
print(big_instance.little_instance.parents_att())
big_instance.attribute1 = 2
print(big_instance.little_instance.parents_att())
You can do the following if you want to access attribute from BigClass into LittleClass.
class BigClass:
def __init__(self, att):
self.attribute1 = att
class LittleClass(BigClass):
def __init__(self, att):
BigClass.__init__(self, att)
def parent_att(self):
return self.attribute1
small_instance = LittleClass(1)
print(small_instance.parent_att)

Derived class with differing interface

The following code:
class Cache:
def __init__(self):
self._cache = []
def store(self, data):
self._cache.append(data)
def stats(self):
print('We are caching {} elements'.format(len(self._cache)))
class LegoCache(Cache):
def store(self, weight, color):
Cache.store(self, (weight, color))
has a problem in that the store method does not implement the interface of the base class.
How can this code be improved? I have the following ideas:
do not derive from Cache, just make use of it.
rename the store method in the base class to store_base
Are there other alternatives?
EDIT
The base class must also support this other use case:
class ZombieCache(Cache):
def store(self, dead_since, humans_eaten, can_be_saved=False):
Cache.store(self, dict(
dead_since=dead_since,
humans_eaten=humans_eaten,
can_be_saved=can_be_saved))
You may use a variable argument list in the base class:
class Cache:
def __init__(self):
self._cache = []
def store(self, *args):
self._cache.append(args)
def stats(self):
print('We are caching {} elements'.format(len(self._cache)))
class LegoCache(Cache):
pass
# "overloading" store isn't needed
So it isn't needed to overload this method or add methods with different names for specials cases:
cache = Cache()
legoCache = LegoCache()
cache.store(x)
legoCache.store(x, y)
Another solution may be delegation:
class LegoCache(object):
def __init__(self):
self.cache = Cache()
def store(self, weight, color):
self.cache.store((weight, color))
# or just self.cache.store(weight, color) if you use the *args implementation
I would implement it like this,
class Cache:
def __init__(self):
self._cache = []
def store(self, data):
self._cache.append(data)
def stats(self):
print('We are caching {} elements'.format(len(self._cache)))
class LegoData(object):
def __init__(self, weight, color):
self.weight = weight
self.color = color
class LegoCache(Cache):
pass
Client will access it like this,
lego_cache = LegoCache()
lego_cache.store(LegoData(weight=10, color='Green'))

Combining dict in super class's init and subclass's init automatically?

I'm creating an event system which uses the following class for events:
class Event(set):
def __init__(self, name, iterable=()):
super().__init__(iterable)
self.name = name
def __iadd__(self, listener):
self.add(listener)
return self
def __isub__(self, listener):
self.remove(listener)
return self
def fire(self, **eargs):
for listener in self:
listener(**eargs)
Now I'm trying to create some kind of a dict that would automatically create the events in its __init__ like so:
class EventDict(dict):
def __init__(self, prefix, *event_names):
super().__init__({
name: Event('%s.%s' % (prefix, name))
for name in event_names
})
And here's an example of usage:
class Player:
def __init__(self, name):
self._name = name
self.events = EventDict('Player', 'change_name')
#property
def name(self):
returns self._name
#name.setter
def name(self, value):
old_name = self.name
self.name = value
self.events['change_name'].fire(player=self, old_name=old_name)
Now the problem I'm facing is subclassing.
If I were to subclass my Player class to include also health attribute, I can't use the same way of creating an event dict, cause it would override the existing one and I couldn't access change_name anymore.
So I'm trying to find a way where I can just do something like this (ideal solution):
class Player:
events = EventDict('Player', 'change_name')
class Player2(Player):
events = EventDict('Player2', 'attack', 'kill')
p2 = Player2()
p2.events['change_name'] += my_event_listener # Still access Player class's events
Would something like this be possible?
I know I can do:
class Player2(Player):
def __init__(self, name):
super().__init__()
self.events.update(...)
But it's not the same :P
I think what you want is:
class Player:
EVENTS = ('change_name',)
def __init__(self, name):
self._name = name
self.events = EventDict(
self.__class__.__name__,
*self.EVENTS,
)
...
Then all you need in Player2 is:
class Player2(Player):
EVENTS = Player.EVENTS + ('attack', 'kill')
and the inherited __init__ will work fine.
Stop using EventDict.
The class itself has its own dict which supports inheritance like that.
class Player:
def __init__(self, name):
self._name = name
self.change_name_event = Event('Player.change_name')
class Player2(Player):
def __init__(self, name):
super().__init__(name)
self.attack_event = Event('Player2.attack')
self.kill_event = Event('Player2.kill')
All the events from the subclasses will be added no matter what.
I noticed that maybe you wanted to make it obvious that they're events, so I added 'event' to the names of the fields, but you don't need to if you don't want to.
If you wanted it so that the prefix is the same throughout, then you'd change the strings from something like 'Player.change_name' to self.__class__.__name__ + '.change_name'. That way, it always gets whatever the actual class for the object is. This is part of what #jonrsharpe's solution is trying to get at.
If you wanted to make it so others could add more events dynamically, they can simply do a line like playerObj.my_new_event = Event('Player.my_new_event') or you could provide a nice method in the Player class to make their lives easier:
def add_event(self, event_name):
setattr(self, event_name, Event(self.__class__.__name__ + '.' + event_name)

Categories

Resources