Ajout du script d'optimisation du trajet boulot <> dodo, copié/collé depuis le Notebook Jupyter correspondant.
This commit is contained in:
parent
4101b497b3
commit
90497dfcfc
146
optimisation_trajet.py
Normal file
146
optimisation_trajet.py
Normal 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)
|
||||
Loading…
Reference in a new issue