optapy / optapy

OptaPy is an AI constraint solver for Python to optimize planning and scheduling problems.

Home Page:https://www.optapy.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to use groupBy?

SeifFeidi opened this issue · comments

I have a list of users with this structure: User (id, date, week_num, user_name),
I want to group all users by week_num and count number of appointments for each user_name to have at least 2 appointments by week for the same user_name.
I tried with countBi (), but I have this error (name 'countBi' is not defined)

You need to import ConstraintCollectors from optapy.constraint and use ConstraintCollectors to get the collector. For instance:

from optapy.constraint import ConstraintCollectors

def each_user_name_must_have_at_least_two_appointments(constraint_factory: ConstraintFactory):
    return (
        constraint_factory.for_each(User)
                                        .group_by(lambda user: user.week_num,
                                                           lambda user: user.user_name,
                                                           ConstraintCollectors.count())
                                        .filter(lambda week, user_name, count: count < 2)
                                        .penalize('User have less than two appointments in a given week',
                                                         HardSoftScore.ONE_HARD)
    ) 

(as for what count method to use, that depends on the cardinality of the stream; see https://www.optapy.org/docs/latest/constraint-streams/constraint-streams.html#collectorsCount for details)

thank you for your help
With this example I have one appointment by week, I want to have one appointment for each user. name by week!!

def nbr_rdv_resp(constraint_factory: ConstraintFactory):
return (
constraint_factory.for_each(User)
.group_by(lambda user: user.dateslot.num_week,
lambda user: user.name,
ConstraintCollectors.count())
.filter(lambda week, name, count: count >= 2)
.penalize('User have less than two appointments in a given week',
HardMediumSoftScore.ONE_HARD)
)

That look like it should work (translating into english, the constraint you written should be equivalent to "penalize having more than one appointment per (user.week_num, user.name) pair by 1 hard"). If you want exactly one (i.e. not 0 or 1), you need to use if_not_exists to penalize the 0 case:

from optapy.constraint import Joiners

def each_user_name_must_have_at_least_one_appointment_per_week(constraint_factory: ConstraintFactory):
    return (
        constraint_factory.for_each(Week)
                                        .join(UserName)
                                        .if_not_exists(User,
                                                                 Joiners.equal(lambda week, user_name: week.week_num,
                                                                                          lambda user: user.week_num),
                                                                 Joiners.equal(lambda week, user_name: user_name.user_name,
                                                                                          lambda user: user.user_name),
                                        )
                                        .penalize('User name does not have an appointment for the given week',
                                                         HardSoftScore.ONE_HARD)
    ) 

(Where Week and UserName are problem fact classes that store information about possible week numbers and user names respectively that have a corresponding @problem_fact_collection_property fields in the @planning_solution)

Have you tested it using constraint_verifier (see https://github.com/optapy/optapy-quickstarts/blob/stable/school-timetabling/tests.py and https://www.optapy.org/docs/latest/constraint-streams/constraint-streams.html#constraintStreamsTesting)