Ajout du script d'optimisation du trajet boulot <> dodo, copié/collé depuis le Notebook Jupyter correspondant.

This commit is contained in:
Cacahuete X240 2018-07-21 22:31:56 +02:00
parent 4101b497b3
commit 90497dfcfc

146
optimisation_trajet.py Normal file
View file

@ -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)