Initial commit

This commit is contained in:
Ludger Heide 2020-10-06 18:29:24 +02:00
commit e0df453875
14 changed files with 642 additions and 0 deletions

241
.gitignore vendored Normal file
View File

@ -0,0 +1,241 @@
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
out.xlsx
animation.mp4

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (venv)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/verschrottungswahrscheinlichkeit.iml" filepath="$PROJECT_DIR$/.idea/verschrottungswahrscheinlichkeit.iml" />
</modules>
</component>
</project>

7
.idea/vcs.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="requirementsPath" value="" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="GOOGLE" />
<option name="myDocStringFormat" value="Google" />
</component>
</module>

50
demo.py Normal file
View File

@ -0,0 +1,50 @@
from util import *
if __name__ == "__main__":
sanity_check_for_subsidy()
population_distribution = steady_state_population()
plot_population(population_distribution)
subsidy_mortality = calculate_subsidy_mortality(population_distribution, DEFAULT_MORTALITY_DISTRIBUTION, total_cars=1e6)
covid_mortality = calculate_covid_mortality(population_distribution, DEFAULT_MORTALITY_DISTRIBUTION)
plot_mortality(DEFAULT_MORTALITY_DISTRIBUTION, subsidy_mortality, covid_mortality)
total_pops = list()
distribs = list()
production = list()
duration = 50
start = 2010
years = np.arange(start, start+duration)
for i in range(duration):
if i < 10:
fraction_of_new_cars = 1.0
mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION
elif i == 10:
fraction_of_new_cars = 1.00
mortality_distribution = covid_mortality
elif i == 11:
fraction_of_new_cars = 1.00
mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION# subsidy_mortality
elif i > 11 and i <= 20:
fraction_of_new_cars = 1.00
mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION
elif i > 20:
fraction_of_new_cars = 1.0
mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION
else:
fraction_of_new_cars = 1.0
mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION
population_distribution = iterate(mortality_distribution, population_distribution, fraction_of_new_cars)
distribs.append(population_distribution.copy())
total_pops.append(sum(population_distribution))
production.append(population_distribution[0])
df = pd.DataFrame(index=years, data=distribs)
df.to_excel("out.xlsx")
plot_population_development(years, total_pops)
plot_production(years, production)
animate(years, distribs)

BIN
input/age distribution.xlsx Normal file

Binary file not shown.

BIN
input/mortality.xlsx Executable file

Binary file not shown.

Binary file not shown.

222
util.py Normal file
View File

@ -0,0 +1,222 @@
import itertools
import logging
import pandas as pd
import numpy as np
from celluloid import Camera
from scipy import stats
import seaborn as sns
from matplotlib import pyplot as plt
DEFAULT_AGES = np.arange(0, 50)
DEFAULT_MORTALITY_DISTRIBUTION = 5 * stats.exponweib(a=1, c=3, loc=0, scale=30).pdf(DEFAULT_AGES)
DEFAULT_TOTAL_POPULATION = 40e6
def steady_state_population(mortality_distribution: np.array = None, total_population: int = DEFAULT_TOTAL_POPULATION):
"""
## Establishing the initial population
We use an array ranging from 0 to 50 years of age. We start with an equal distribution and run the simulation until
it arrives at the steady-state solution. _In reality, we'd use the real data here_.
Args:
mortality_distribution: The chance a vehicle that is _x_ years old leaves the population
total_population: The total vehicle population
Returns:
The steady-state population distribution, splitting the `total_population` vehicles across their ages.
"""
# Initialize mortality
if mortality_distribution is None:
mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION
# Initialize population with uniform distribution
population_distribution = np.ones(mortality_distribution.shape) * (total_population / len(mortality_distribution))
old_population_distribution = np.zeros(mortality_distribution.shape)
i = 0
while not np.all(np.isclose(population_distribution, old_population_distribution)):
old_population = sum(population_distribution)
old_population_distribution = population_distribution.copy()
i += 1
# First, simulate the mortality and keep count of the new vehicles we need to produce to replace the existing ones
new_cars = population_distribution[-1]
for j in reversed(range(len(mortality_distribution) - 1)):
population_distribution[j + 1] = (1 - mortality_distribution[j]) * population_distribution[j]
new_cars += (mortality_distribution[j] * population_distribution[j])
# Last, add new vehicles in otder for the sum to make sense
population_distribution[0] = 0
population_before_creation = sum(population_distribution)
new_cars = ((old_population) + 0) - population_before_creation
population_distribution[0] = new_cars
logger = logging.getLogger(__name__)
logger.info(f"Steady-state population achieved after {i} iterations.")
return population_distribution
def calculate_subsidy_mortality(steady_state_population: np.array, normal_mortality_distribution: np.array,
min_age: int = 10, total_cars: int = 1e6):
"""
Calculates a mortality distribution for the subsidy year so that `total_cars` with an age greater or equal then
`min_age` leave the population.
"""
# Count the total cars older than the minimum age:
total_old_cars = np.sum(steady_state_population[min_age:])
assert total_old_cars > total_cars, "You cannot recycle more vehicles than exist."
# Calculate which fraction of them needs to be recycles
recycling_fraction = total_cars/total_old_cars
subsidy_mortality_distribution = normal_mortality_distribution.copy()
for i in np.arange(min_age, len(steady_state_population)):
subsidy_mortality_distribution[i] += recycling_fraction
assert subsidy_mortality_distribution[i] <= 1, "You cannot recycle more vehicles than exist."
return subsidy_mortality_distribution
def calculate_covid_mortality(steady_state_population: np.array, normal_mortality_distribution: np.array,
reduction_in_production: int = 0.23):
"""
Calculates a mortality distribution for the subsidy year so that `total_cars` with an age greater or equal then
`min_age` leave the population.
"""
return normal_mortality_distribution * (1-reduction_in_production)
def iterate(mortality_distribution: np.array, population_distribution: np.array, fraction_of_new_cars: float = 1.0):
old_population = sum(population_distribution)
# First, simulate the mortality
for j in reversed(range(len(mortality_distribution) - 1)):
population_distribution[j + 1] = (1 - mortality_distribution[j]) * population_distribution[j]
# Last, add new vehicles in order for the sum to make sense
population_distribution[0] = 0
population_before_creation = sum(population_distribution)
new_cars = old_population - population_before_creation
population_distribution[0] = new_cars * fraction_of_new_cars
return population_distribution
def plot_mortality(mortality_distribution = DEFAULT_MORTALITY_DISTRIBUTION, subsidy_distribution = None,
covid_distribution = None):
logging.basicConfig(level=logging.DEBUG)
plt.figure()
if subsidy_distribution is None:
plot_ = sns.barplot(np.arange(len(mortality_distribution)), mortality_distribution, color='cornflowerblue')
elif covid_distribution is None:
df = pd.DataFrame({"Ages": np.hstack((np.arange(len(mortality_distribution)), np.arange(len(mortality_distribution)))),
"label": list(itertools.chain(["Normal"]*len(mortality_distribution), ["With Subsidy"]*len(mortality_distribution))),
"Legend": np.hstack((mortality_distribution, subsidy_distribution))})
else:
df = pd.DataFrame({"Ages": np.hstack((np.arange(len(mortality_distribution)),
np.arange(len(mortality_distribution)),
np.arange(len(mortality_distribution)))),
"label": list(itertools.chain(["Normal"] * len(mortality_distribution),
["With Subsidy"] * len(mortality_distribution),
["With Covid"] * len(mortality_distribution))),
"Scenario": np.hstack((mortality_distribution,
subsidy_distribution,
covid_distribution))})
plot_ = sns.barplot(data=df, x="Ages", y="Scenario", hue="label")
plt.title("Mortality Distribution")
plt.ylabel("Recycling probability")
plt.xlabel("Age (years)")
for ind, label in enumerate(plot_.get_xticklabels()):
if ind % 10 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.show()
plt.close()
def plot_population(population_distribution: np.ndarray):
plt.figure()
plot_ = sns.barplot(np.arange(len(population_distribution)), population_distribution / 1e6, color='cornflowerblue')
plt.title("Population Distribution")
plt.ylabel("Vehicles (Millions)")
plt.xlabel("Age (years)")
for ind, label in enumerate(plot_.get_xticklabels()):
if ind % 10 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.show()
plt.close()
def sanity_check_for_subsidy():
population_distribution = steady_state_population()
subsidy_mortality = calculate_subsidy_mortality(population_distribution, DEFAULT_MORTALITY_DISTRIBUTION)
new_population_distribution = iterate(DEFAULT_MORTALITY_DISTRIBUTION, population_distribution)
normal_year_new_cars = new_population_distribution[0]
subsidy_population_distribution = iterate(subsidy_mortality, population_distribution)
subsidy_new_cars = subsidy_population_distribution[0]
assert np.abs((subsidy_new_cars - normal_year_new_cars) - 1e6) < 10000
def plot_population_development(years, total_pops: np.array):
# Plot total vehicle pop
plt.figure()
plot_ = sns.barplot(years, np.array(total_pops) / 1e6, color='cornflowerblue')
plt.title("Total Vehicle Population")
plt.ylabel("Vehicles (Millions)")
plt.xlabel("Simulation year")
plt.ylim(min(np.array(total_pops) / 1e6), max(np.array(total_pops) / 1e6))
for ind, label in enumerate(plot_.get_xticklabels()):
if ind % 10 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.show()
plt.close()
def plot_production(years, production: np.array):
df = pd.DataFrame({"Year": np.hstack((years, years)),
"Production": np.hstack((production, [production[0]]*len(years))),
"Scenario": itertools.chain([f"Subsidy ({np.sum(production)/1e6})"]*len(years), [f"Business as Usual ({(production[0]/1e6)*len(years)})"]*len(years))})
df.Production /= 1e6
# Plot total vehicle pop
plt.figure()
plot_ = sns.barplot(data=df, x="Year", y="Production", hue="Scenario")
plt.title("Total Vehicle Production")
plt.ylabel("Vehicles (Millions)")
plt.xlabel("Simulation year")
plt.ylim(0.8*min(np.array(production) / 1e6), 1.2*max(np.array(production) / 1e6))
for ind, label in enumerate(plot_.get_xticklabels()):
if ind % 10 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.show()
plt.close()
def animate(years, population_distributions):
ages = np.arange(len(population_distributions[0]))
fig = plt.figure()
camera = Camera(fig)
# animation draws one data point at a time
for i in range(len(population_distributions)):
plot_ = sns.barplot(ages, population_distributions[i] / 1e6, color='cornflowerblue')
plt.plot(ages, np.array(population_distributions[0]) / 1e6, color='blue')
for ind, label in enumerate(plot_.get_xticklabels()):
if ind % 10 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.title(f"Total Vehicle Population")
plt.ylabel("Vehicles (Millions)")
plt.xlabel("Vehicle Age")
camera.snap()
anim = camera.animate(blit=False)
anim.save('animation.mp4')

71
verify_real_data.py Normal file
View File

@ -0,0 +1,71 @@
from util import *
def real_mortality_distribution() -> np.ndarray:
df = pd.read_excel("mortality.xlsx")
mortality = df.Mortality
mortality[0] = 0
mortality[1] = 0
mortality[2] = 0
mortality[32:38] = mortality[31]
mnp.hstack((mortality, mortality[31]))
return np.array(-1*mortality)
def real_population_distribution() -> np.ndarray:
df = pd.read_excel("age distribution.xlsx")
return np.array(df["Absolute Anzahl "])
def population_distribution_deltas() -> np.ndarray:
population_distribution = real_population_distribution()
mortality_distribution = real_mortality_distribution()
df = pd.read_excel("new registration numbers.xlsx")
years = np.arange(2020, 2031)
new_registrations = np.array(df["scenario without Covid"][1:])
steady_state_registrationss = list()
correction_factors = list()
totals = list()
for i in range(len(years)):
steady_state_registrations = iterate(real_mortality_distribution(), population_distribution.copy())[0]
correction_factor = new_registrations[i]/steady_state_registrations
correction_factors.append(correction_factor)
population_distribution = iterate(real_mortality_distribution(), population_distribution, fraction_of_new_cars=correction_factor)
totals.append(sum(population_distribution))
steady_state_registrationss.append(steady_state_registrations)
df = pd.DataFrame({"Year": years,
"Predicted Regsitrations": new_registrations,
"Steady State Registrations": steady_state_registrationss,
"Correction Factor": correction_factors,
"Total": totals})
return correction_factors
if __name__ == "__main__":
mortality_distribution = real_mortality_distribution()
population_distribution = real_population_distribution()
steady_state_population_distribution = steady_state_population(mortality_distribution,
total_population=sum(population_distribution))
if True:
plt.figure()
df = pd.DataFrame({"Age": np.hstack((np.arange(len(mortality_distribution)),
np.arange(len(mortality_distribution)))),
"Data": np.hstack((population_distribution/1e6,
steady_state_population_distribution/1e6)),
"Scenario": np.hstack((["KBA Data"]*len(mortality_distribution),
["Simulated Steady State"]*len(mortality_distribution)))})
plot_ = sns.barplot(data=df, x="Age", y="Data", hue="Scenario")
plt.title("Population Distribution")
plt.ylabel("Vehicles (Millions)")
plt.xlabel("Age (years)")
for ind, label in enumerate(plot_.get_xticklabels()):
if ind % 10 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.show()
plt.close()
population_distribution_deltas()