commit 88b3e5f5d6f5e416db6985838acb6d69cdfb4ac9
parent 75ea1d1d52247755eafe6cc791ec2727655a101b
Author: Antoine Amarilli <a3nm@a3nm.net>
Date: Tue, 2 Sep 2025 16:20:20 +0200
2025
Diffstat:
7 files changed, 653 insertions(+), 0 deletions(-)
diff --git a/2025/LICENSE b/2025/LICENSE
@@ -0,0 +1,18 @@
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/2025/README.md b/2025/README.md
@@ -0,0 +1,110 @@
+This file explains how the carbon footprint of Highlights'25 was computed.
+
+## Data collection
+
+We collected information about the travel plans of participants via the
+registration form, containing the following fields:
+
+- Arriving from…
+- Arriving by…
+- Leaving to...
+- Leaving by…
+- Other scientific activities during your stay
+- I am planning to attend the Highlights Collaborative Research Week (September 6 – September 12, no extra fees)
+
+We only kept participants with status "paid" and with an onsite ticket, we
+arrive at 144 on-site participants. We checked from the email addresses that
+there are no duplicate email addresses, and from the names that there are no
+duplicate names.
+
+We note that the transportation mode was not indicated by all participants: out
+of 144 participants, 24 did not specify it at all, and 3 more specified it only
+partially.
+
+We eliminated participants who were apparently local, and 130 onsite nonlocal
+participants remain.
+
+We completed the travel destinations of the participants when missing or when
+obviously inconsistent, using the location of their affiliation.
+
+We prepared a file with the following tab-separated fields:
+- field 0 is the origin
+- field 1 is the transportation mode
+- field 2 is the origin
+- field 3 is the transportation mode
+- field 4 is the information about whether the participant is extending their
+ trip for other scientific reasons
+- field 5 is the information about whether the participant is attending HCRW
+
+(These files are not versioned because they can be considered private
+information.)
+
+We run:
+
+ ./generate_trips.py 49.2569809 7.0420618 0.2
+
+Where the arguments are the latitude and longitude of Bordeaux, and 0.2 is the
+noise to add. This generates a file trips_anonymized.csv containing, for each
+trip leg, the mode ("plane", "train", "bus/coach"), the distance (in km,
+rounded, with noise), and the information about extended stays. A file
+trips.csv is also produced for debugging (with the data without noise and with
+personal information). A file map.geojson is also produced with the map of
+participants and transportation modes and private information (to be used as an
+image only).
+
+The file trips_anonymized.csv can then be fed to co2.py which computes the
+carbon footprint (see below). This gives (from the anonymized data):
+
+total CO2e emissions (tons): 29.158425
+for mode plane: CO2e emissions (tons): 26.689530
+for mode train: CO2e emissions (tons): 2.428939
+for mode bus/coach: CO2e emissions (tons): 0.039956
+for distances <2000 km, plane is used for 54/239 trips
+for distances >=2000 km, plane is used for 21/21 trips
+flights of over 2000 km account for 15.987136 CO2e emissions (tons) i.e. 54.828531 percent of total for 21/260 total legs
+distance by plane: 143539
+num by mode plane: 75
+num by mode train: 177
+num by mode bus/coach: 8
+dist by mode plane: 143539
+dist by mode train: 65647
+dist by mode bus/coach: 1427
+
+Hence, the total CO2 footprint is 29 tons CO2e (it is the same with the
+non-anonymized file). Around 92% of emissions are due to plane travel, and 55%
+of the emissions are due to 8% of the transportation legs, namely,
+the plane trips of over 2000 km. (All trips of more than 2000 km are done by
+plane)
+
+The average footprint per onsite non-local participant (130) is around
+224 kgCO2e. The average footprint per onsite participant (144) is around
+202 kgCO2e. (These figures are computed from the anonymized data.)
+
+### Carbon footprint (unchanged from 2024)
+
+Like in 2022, we compute the CO2 fotprint following the
+[labos1point5](https://labos1point5.org/ges-1point5) data, which is adapted from
+the French agency [Ademe](https://www.ademe.fr/). We use the values from 2022
+without updating them to ensure that the methodology is comparable.
+
+- For train, we count **37 gCO2e/pkm** (international train). This is pessimistic in France, very
+ pessimistic for TGV, but similar to the 41 gCO2e/pm for national (UK) rail
+ given by [Our World in
+ Data](https://ourworldindata.org/travel-carbon-footprint).
+- Plane is counted following
+ [labos1point5](https://labos1point5.org/ges-1point5), including the effect
+ of contrails:
+ - 258 gCO2e/pkm for less than 1000km
+ - 187 gCO2e/pkm between 1001km and 3500km
+ - 152 gCO2e/pkm above 3500km. This value is consistent to the 150 gCO2e/pkm
+ value for long-haul flight given by [Our World in
+ Data](https://ourworldindata.org/travel-carbon-footprint) (also including
+ contrails)
+- For bus/coach, we count 28 gCO2e/pkm as the coach value given by [Our World in
+ Data](https://ourworldindata.org/travel-carbon-footprint) as there is no
+ value in labos1point5.
+
+## Trends relative to 2024
+
+The number of onsite participants is a little higher, and the number of nonlocal onsite participants is a little lower. The footprints per nonlocal participant are significantly lower -- perhaps the conference venue is more central?
+
diff --git a/2025/co2.py b/2025/co2.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+
+# From a list of trip legs (mode, distance_in_km), compute the total CO2
+# footprint and statistics
+
+import sys
+from collections import defaultdict
+
+LONG_THRESH = 2000
+
+def co2(distance, mode):
+ if mode == "train":
+ g_km_person = 37
+ if mode == "bus/coach":
+ g_km_person = 28
+ if mode == "plane":
+ if distance <= 1000:
+ g_km_person = 258
+ elif 1000< distance <= 3500:
+ g_km_person = 187
+ elif 3500< distance:
+ g_km_person = 152
+ return distance * g_km_person
+
+co2_by_mode = defaultdict(lambda: 0)
+co2_total = 0
+co2_total_long_plane = 0
+num_total_long_plane = 0
+num_total_long_nonplane = 0
+num_total = 0
+num_short_plane = 0
+num_short = 0
+dist_plane = 0
+km_by_mode = defaultdict(lambda: 0)
+num_by_mode = defaultdict(lambda: 0)
+
+with open("footprints.txt", 'w') as fp:
+ for l in sys.stdin.readlines():
+ f = l.strip().split(",")
+ mode = f[0]
+ dist = float(f[1])
+ co2v = co2(dist, mode)
+ co2_by_mode[mode] += co2v
+ print(co2v, file=fp)
+ co2_total += co2v
+ num_total += 1
+ num_by_mode[mode] += 1
+ km_by_mode[mode] += dist
+ if mode == "plane":
+ dist_plane += dist
+ if dist >= LONG_THRESH:
+ if mode == "plane":
+ co2_total_long_plane += co2v
+ num_total_long_plane += 1
+ else:
+ num_total_long_nonplane += 1
+ if dist < LONG_THRESH:
+ num_short += 1
+ if mode == "plane":
+ num_short_plane += 1
+
+assert (num_short + num_total_long_plane + num_total_long_nonplane == num_total)
+
+print("total CO2e emissions (tons): %f" % (co2_total/1000000))
+for m in co2_by_mode.keys():
+ print("for mode %s: CO2e emissions (tons): %f" % (m, co2_by_mode[m]/1000000))
+
+print ("for distances <%d km, plane is used for %d/%d trips" %
+ (LONG_THRESH, num_short_plane, num_short))
+print ("for distances >=%d km, plane is used for %d/%d trips" %
+ (LONG_THRESH, num_total_long_plane, num_total_long_plane+num_total_long_nonplane))
+
+print( "flights of over %d km account for %f CO2e emissions (tons) i.e. %f percent of total for %d/%d total legs"
+ % (LONG_THRESH, co2_total_long_plane/1000000, 100*co2_total_long_plane/co2_total,
+ num_total_long_plane, num_total))
+print("distance by plane: %d" % dist_plane)
+
+for k in num_by_mode.keys():
+ print("num by mode %s: %d" % (k, num_by_mode[k]))
+for k in km_by_mode.keys():
+ print("dist by mode %s: %d" % (k, km_by_mode[k]))
diff --git a/2025/commands.txt b/2025/commands.txt
@@ -0,0 +1,16 @@
+# Here are the raw operations that were used to process the data
+# Note that the registation file is not versioned here because it contains
+# personal information
+
+- select the right columns in libreoffice
+- replace the newlines in libreoffice
+- cat high2025_orders_2025_08_29_filter.csv| grep '^paid;' | grep -v "Online only ticket" | cut -d';' -f2,3,4,6- > onsites.csv
+ - checked that the other statuses than "paid" look bad
+ - checked that the prices paid make sense
+ - checked that there are no duplicates
+- eliminate local participants, yielding onsites_anon_nolocal.csv
+- manually complete locations, yielding onsites_anon_nolocal_completed.csv
+- cut -d';' -f7-12 onsites_anon_nolocal_completed.csv > location_mode_extension.csv
+- cat location_mode_extension.csv| cut -d';' -f1 > locations.txt
+- cat location_mode_extension.csv| cut -d';' -f3 >> locations.txt
+- cat locations.txt| sort | uniq > locations_uniq.txt
diff --git a/2025/generate_trips.py b/2025/generate_trips.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+
+# Process registration data to generate the list of trip legs
+
+import csv
+import sys
+import json
+from geopy.distance import geodesic
+from collections import defaultdict
+from random import uniform
+
+# place of the conference
+origin = (sys.argv[1], sys.argv[2])
+noise = float(sys.argv[3]) # how much multiplicative noise to add to distances
+
+# read locations
+location = {}
+with open("locations_with_latlon.txt", 'r') as floc:
+ for l in floc.readlines():
+ f = l.strip().split(' ')
+ lat = f[0]
+ lon = f[1]
+ loc = ' '.join(f[2:])
+ location[loc] = (lat, lon)
+
+FNAME = "location_mode_extension.csv"
+
+modes = ["train", "plane", "bus/coach", "other", ""]
+places = defaultdict(lambda : [0, 0, ""])
+
+def complete_mode(dist):
+ if dist > 400:
+ return "plane"
+ else:
+ return "train"
+
+# compute trips
+with open("trips_anonymized.csv", 'w') as fout:
+ with open("trips.csv", 'w') as fout2:
+ with open(FNAME, 'r') as ftrip:
+ reader = csv.reader(ftrip, delimiter=";")
+ for r in reader:
+ #university = r[5]
+ # first = r[2].replace(',', '')
+ # last = r[3].replace(',', '')
+ # assert(r[6] == "I'm coming to Bordeaux")
+ # assert(r[8] == "External Participant")
+ # typ = r[7].replace(',', '')
+ from_place = r[0].strip()
+ from_mode = r[1].replace(',', '').lower()
+ to_place = r[2].strip()
+ to_mode = r[3].replace(',', '').lower()
+ if r[4].startswith("Yes"):
+ extended_1 = 'X'
+ else:
+ extended_1 = ''
+ if r[5].startswith("Yes"):
+ extended_2 = 'X'
+ else:
+ extended_2 = ''
+ #annotation = ' '.join((first, last, university, typ))
+ annotation = ''
+ assert (from_mode in modes)
+ assert (to_mode in modes)
+ from_coord = location[from_place]
+ to_coord = location[to_place]
+
+ from_dist = geodesic(origin, from_coord).kilometers
+ to_dist = geodesic(origin, to_coord).kilometers
+ from_dist_anon = round(uniform(from_dist * (1-noise), from_dist * (1+noise)))
+ to_dist_anon = round(uniform(to_dist * (1-noise), to_dist * (1+noise)))
+ if from_mode == '' or from_mode == 'other':
+ from_mode = complete_mode(from_dist)
+ if to_mode == '' or to_mode == 'other':
+ to_mode = complete_mode(to_dist)
+
+ places[from_coord][1] += 1
+ places[to_coord][1] += 1
+ places[from_coord][2] += annotation + "\n"
+ places[to_coord][2] += annotation + "\n"
+ if from_mode == "plane":
+ places[from_coord][0] += 1
+ if to_mode == "plane":
+ places[to_coord][0] += 1
+
+
+ print(','.join((
+ from_mode.lower(),
+ str(from_dist),
+ extended_1, extended_2,
+ from_place.replace(',', ''),
+ *from_coord, annotation)), file=fout2)
+ print(','.join((
+ to_mode.lower(),
+ str(to_dist),
+ extended_1, extended_2,
+ to_place.replace(',', ''),
+ *to_coord, annotation)), file=fout2)
+ print(','.join((
+ from_mode.lower(),
+ str(from_dist_anon),
+ extended_1, extended_2)), file=fout)
+ print(','.join((
+ to_mode.lower(),
+ str(to_dist_anon),
+ extended_1, extended_2)), file=fout)
+
+ ## OUTPUT GEOJSON
+
+ features = []
+ for k in places.keys():
+ red = int(255.*places[k][0]/places[k][1])
+ green = 0
+ blue = int(255.*(places[k][1]-places[k][0])/places[k][1])
+ color = '#%02X%02X%02X' % (red, green, blue)
+ feature = {
+ "type": "Feature",
+ "properties": {
+ "name":places[k][2],
+ "_umap_options": {"color": color}
+ },
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ k[1], k[0]
+ ]
+ }
+ }
+ features.append(feature)
+
+output = {
+ "type": "FeatureCollection",
+ "features": features
+}
+
+with open("map.geojson", 'w') as f:
+ print (json.dumps(output), file=f)
+
diff --git a/2025/geocode.py b/2025/geocode.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+# read human-readable locations on stdin, produce same list with added GPS
+# coordinates on STDOUT
+
+from geopy.geocoders import GeoNames
+import os
+import sys
+from time import sleep
+
+USER="a3nm" # user on geonames.org, serves as API key
+geolocator = GeoNames(username=USER)
+
+def searchGeonames(place):
+ # chatgpt
+ global geolocator
+ location = geolocator.geocode(place, exactly_one=True)
+ if location is None:
+ print("Error: no result for %s" % place, file=sys.stderr)
+ sys.exit(42)
+ return (location.latitude, location.longitude)
+
+for l in sys.stdin.readlines():
+ l = l.strip()
+ origin_lat, origin_lng = searchGeonames(l)
+
+ print(origin_lat, origin_lng, l)
+
+ sleep(1)
+
diff --git a/2025/trips_anonymized.csv b/2025/trips_anonymized.csv
@@ -0,0 +1,260 @@
+plane,739,,
+plane,888,,
+train,496,,
+train,617,,
+train,1186,,
+train,887,,
+train,839,X,
+train,300,X,
+bus/coach,66,,
+bus/coach,50,,
+train,834,X,
+plane,2571,X,
+train,278,,
+train,274,,
+plane,502,,
+plane,525,,
+train,503,X,X
+train,536,X,X
+bus/coach,305,,
+bus/coach,269,,
+train,895,,
+train,314,,
+train,205,,X
+train,232,,X
+train,622,,
+train,749,,
+train,760,,
+train,709,,
+train,400,,
+train,393,,
+plane,1099,,
+plane,1063,,
+plane,1061,X,
+plane,808,X,
+train,375,,
+train,396,,
+plane,2511,,
+plane,3016,,
+train,548,,
+train,330,,
+train,357,,X
+train,394,,X
+plane,556,,
+plane,551,,
+train,256,,
+train,303,,
+train,974,,
+train,965,,
+plane,923,X,
+plane,1038,X,
+plane,7095,X,X
+plane,7735,X,X
+plane,593,X,
+train,48,X,
+train,337,,
+train,383,,
+train,433,,
+train,386,,
+plane,942,,
+plane,997,,
+train,225,,
+train,219,,
+plane,1021,,
+plane,939,,
+plane,5784,X,
+plane,7339,X,
+train,325,,
+train,287,,
+train,1032,,
+train,739,,
+train,160,,
+train,212,,
+train,811,,
+train,313,,
+train,47,,
+train,49,,
+plane,502,,
+plane,391,,
+train,333,,
+train,408,,
+train,774,,
+train,681,,
+train,47,,
+train,3,,
+train,812,,
+train,781,,
+train,1000,,
+train,768,,
+train,219,,
+train,292,,
+train,882,,X
+train,759,,X
+plane,583,,
+plane,794,,
+train,796,,
+train,604,,
+train,767,,
+train,759,,
+train,60,,
+train,56,,
+train,225,,
+train,213,,
+plane,817,,
+plane,957,,
+train,345,,
+train,373,,
+train,316,,
+train,668,,
+train,390,,
+train,383,,
+train,236,,
+train,339,,
+plane,927,,
+train,392,,
+train,258,,
+train,259,,
+train,373,,X
+train,324,,X
+plane,1053,,X
+plane,1201,,X
+train,945,,
+train,965,,
+train,271,,
+train,274,,
+train,351,,
+train,333,,
+train,101,X,
+train,87,X,
+train,558,,
+train,339,,
+plane,2847,X,X
+plane,2371,X,X
+train,237,X,X
+train,216,X,X
+plane,1334,X,X
+plane,1115,X,X
+train,407,,
+train,396,,
+train,16,,X
+train,12,,X
+plane,833,,
+plane,1176,,
+train,53,X,X
+train,67,X,X
+plane,2822,,
+plane,2414,,
+train,677,,
+train,257,,
+plane,937,X,X
+plane,1134,X,X
+train,377,,
+train,368,,
+train,274,,X
+train,321,,X
+plane,8117,X,X
+plane,8316,X,X
+train,311,,
+train,303,,
+plane,2720,,
+plane,3313,,
+train,887,,
+train,1007,,
+train,408,,
+train,280,,
+plane,8288,X,X
+plane,7567,X,X
+plane,714,,
+plane,644,,
+train,163,,
+train,176,,
+train,319,,
+train,294,,
+train,402,X,X
+train,426,X,X
+train,67,,
+train,66,,
+train,210,,
+train,282,,
+train,97,,
+train,71,,
+bus/coach,327,,
+bus/coach,290,,
+plane,3486,,
+plane,2729,,
+train,53,X,
+train,62,X,
+train,62,,X
+train,59,,X
+train,48,,
+train,49,,
+plane,670,,
+plane,803,,
+train,55,,X
+train,62,,X
+train,330,,X
+train,292,,X
+plane,582,,X
+plane,739,,X
+train,157,,
+train,210,,
+plane,989,,X
+plane,1027,,X
+train,186,,
+train,166,,
+train,112,,
+train,93,,
+train,642,X,X
+train,650,X,X
+plane,469,,
+plane,474,,
+plane,969,,
+plane,868,,
+plane,1109,,
+plane,908,,
+train,312,,
+train,369,,
+plane,841,,
+plane,958,,
+plane,1108,,
+plane,1245,,
+train,105,,
+train,88,,
+train,681,,
+train,752,,
+train,350,,
+train,283,,
+bus/coach,53,,
+bus/coach,67,,
+plane,2816,X,
+plane,3581,X,
+train,767,,
+train,769,,
+train,54,,
+train,63,,
+plane,807,,
+plane,963,,
+train,553,,
+train,458,,
+train,1148,X,
+train,218,X,
+train,350,X,X
+train,162,X,X
+train,52,,
+train,50,,
+train,143,,
+train,182,,
+plane,617,,
+plane,598,,
+train,304,,
+train,248,,
+train,63,,
+train,65,,
+train,253,,
+train,302,,
+train,65,,
+train,54,,
+train,48,,
+train,53,,
+train,223,,
+train,273,,