From 90497dfcfc3229238786ddbeb1ee0d44867d99c6 Mon Sep 17 00:00:00 2001 From: Cacahuete X240 Date: Sat, 21 Jul 2018 22:31:56 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20du=20script=20d'optimisation=20du=20tra?= =?UTF-8?q?jet=20boulot=20<>=20dodo,=20copi=C3=A9/coll=C3=A9=20depuis=20le?= =?UTF-8?q?=20Notebook=20Jupyter=20correspondant.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- optimisation_trajet.py | 146 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 optimisation_trajet.py diff --git a/optimisation_trajet.py b/optimisation_trajet.py new file mode 100644 index 0000000..c091c3a --- /dev/null +++ b/optimisation_trajet.py @@ -0,0 +1,146 @@ +"""Optimisation du trajet boulot <> dodo + +Les but de ce _programme_ est de déterminer la combinaison de transports +optimale sur un mois donné pour minimiser le coût récurrent de trajet +vers et depuis le travail. + +Les 3 modes de transport sont : +- Voiture individuelle +- Transports en communs (sans abonnement) +- Transports en communs (avec abonnement) + +On pourra envisager d'ajouter le covoiturage quand l'offre pour ce mode +sera plus développée / fiable dans le coin. +""" + +from itertools import product +from datetime import datetime, date, timedelta + +import pandas as pd + + +def easter_dates(thisdate): + """This function returns a 3-tuple containing, for the year of thisdate, + Easter day, Ascension and Pentecost as datetime. + It is computed with the Butcher algorithm, from Wikipedia. + """ + y = thisdate.year + _, n = divmod(y, 19) # cycle de Méton + c, u = divmod(y, 100) # centaine et range de l'année + s, t = divmod(c, 4) # siècle bissextile + p, _ = divmod((c+8), 25) # cycle de proemptose + q, _ = divmod((c-p+1), 3) # proemptose + _, e = divmod((19*n+c-s-q+15), 30) # épacte + b, d = divmod(u, 4) # année bissextile + _, l = divmod((2*t+2*b-e-d+32), 7) # lettre dominicale + h, _ = divmod((n+11*e+22*l), 451) # correction + m, j = divmod((e+l-7*h+114), 31) # mois et quantième du Samedi saint + + easter = date(y, m, j+2) # Fêté le lendemain, lundi + ascension = easter + timedelta(days=38) # 40 jrs après Pâques, jeudi + pentecost = easter + timedelta(days=49) # Fêté le lendemain, lundi + + return easter, ascension, pentecost + + +def is_business_day(thisdate, bridge=True): + """This function returns True if the given date is not a public holiday + nor a weekend. If bridge is set to True, returns True if the date is not + between two days off (weekend / public holiday). + """ + # Public holidays + easter, ascension, pentecost = easter_dates(thisdate) + public_holidays = { + date(thisdate.year, 1, 1), # 1er janvier jour de l'an + date(thisdate.year, 5, 1), # 1er mai fête du travail + date(thisdate.year, 5, 8), # 8 mai fête de la Victoire + date(thisdate.year, 7, 14), # 14 juillet fête nationale + date(thisdate.year, 8, 15), # 15 août Assomption + date(thisdate.year, 11, 1), # 1er novembre Toussaint + date(thisdate.year, 11, 11), # 11 novembre armistice + date(thisdate.year, 12, 25), # 25 décembre Noël + easter, # lundi de Pâques + ascension, # jeudi de l'Ascension + pentecost, # lundi de Pentecôte + } + if bridge: + td = timedelta(days=1) + hb = not is_business_day(thisdate-td, bridge=False) # Holiday Before + hn = not is_business_day(thisdate+td, bridge=False) # Holiday Next + bridged = hb and hn + else: + bridged = False + + holiday = thisdate in public_holidays + business = thisdate.weekday() < 5 and not (holiday or bridged) + + return business + + +# On pourrait en faire un générateur... +def combinations(nb_items, total, start_at=0): + """This function returns a colleciton of nb_items-tuples so + that their sum is always equal to total. + """ + r = product(range(start_at, total+1), repeat=nb_items) + r = list(filter(lambda c: sum(c) == total, r)) + return r + + +def count_business_days(year, month, holidays={}): + """This function returns the number of working daysin the given month.""" + bd_count = 0 + for i in range(1,32): + try: + thisdate = date(year, month, i) + except(ValueError): + # no more days in month + break + if is_business_day(thisdate) and not thisdate in holidays: + bd_count += 1 + return bd_count + + +if __name__ == "__main__": + """Les données + + Pas de coût initial pour le T+ ou la Voiture (_on ne prend pas en compte + l'entretien/l'assurance, au moins dans un premier temps_). Le Navigo est + payé en début de mois, à moitié prix car remboursé par l'employeur. + + Le coût récurrent du Navigo doit prendre en compte le prix de l'essence + pour la mobylette, puis on a un prix unitaire pour les T+ et une consom- + -mation rapportée au prix du diesel pour la voiture. + + * Voiture : 5,6 l/100 à 1,44 €/l et pour 2x38 km + * 6,13 €/A-R + * Mobylette : 3,7 l/100 à 1,56 €/l et pour 2x10 km + * 1,15 €/A-R + """ + modes = ('Ticket T+', 'Navigo', 'Voiture') + cout_initial = (0.0, 35.0, 0.0) + cout_recurent = (5.96, 1.15, 6.13) + + annee, mois = 2018, 6 + + jrs_ouvres = count_business_days(annee, mois) + nb_modes = len(modes) + + cout_mix = list() + comb_mix = combinations(nb_modes, jrs_ouvres) + for mix in comb_mix: + cout = 0 + for mode in range(nb_modes): + # TODO: utiliser une expression ternaire + if mix[mode]: + cout += cout_initial[mode] + cout += mix[mode]*cout_recurent[mode] + cout_mix.append(cout) + + bilan = [[m for m in modes] + ['Cout']] + #bilan.append([]) + for i, mix in enumerate(comb_mix): + bilan.append([*mix,cout_mix[i]]) + + bilan_df = pd.DataFrame(bilan) + print(bilan_df)