Lesson 1 of 3
Combine lists, dictionaries and functions to store a small materials database, calculate derived properties, and select materials that meet design requirements.
About 30 minutes
sorted() with key=This module applies everything from the course to realistic materials problems. A common first step in any computational project is deciding how to represent the data. For a collection of materials, each with several named properties, a natural choice is a list of dictionaries: each dictionary is one material's record, and the list holds the whole dataset.
materials = [ {"name": "Aluminium 6061", "density": 2.70, "youngs_modulus": 69, "yield_strength": 276, "cost": 2.5}, {"name": "Titanium Ti-6Al-4V", "density": 4.43, "youngs_modulus": 114, "yield_strength": 880, "cost": 25.0}, {"name": "Steel AISI 4140", "density": 7.85, "youngs_modulus": 205, "yield_strength": 655, "cost": 1.2}, {"name": "Magnesium AZ31", "density": 1.77, "youngs_modulus": 45, "yield_strength": 200, "cost": 4.0}, {"name": "Copper C11000", "density": 8.96, "youngs_modulus": 117, "yield_strength": 70, "cost": 8.0},]# Units: density g/cm³, modulus GPa, yield strength MPa, cost $/kgprint(len(materials), "materials")print(materials[1]["name"], "yields at", materials[1]["yield_strength"], "MPa")Record your units
density_g_cm3), or both.Engineers often care less about a raw property than about a combination. For a lightweight part, what matters is strength or stiffness per unit mass: the specific strength (yield strength ÷ density) and specific stiffness (Young's modulus ÷ density). Small functions that take a record and return a derived value keep these formulas in one tested place.
def specific_strength(material): """Yield strength divided by density, in MPa per (g/cm³).""" return material["yield_strength"] / material["density"]def specific_stiffness(material): """Young's modulus divided by density, in GPa per (g/cm³).""" return material["youngs_modulus"] / material["density"]materials = [ {"name": "Aluminium 6061", "density": 2.70, "youngs_modulus": 69, "yield_strength": 276}, {"name": "Steel AISI 4140", "density": 7.85, "youngs_modulus": 205, "yield_strength": 655}, {"name": "Titanium Ti-6Al-4V", "density": 4.43, "youngs_modulus": 114, "yield_strength": 880},]print(f"{'Material':<20}{'Spec. strength':>16}{'Spec. stiffness':>17}")for material in materials: print(f"{material['name']:<20}{specific_strength(material):>16.1f}{specific_stiffness(material):>17.1f}")Steel is the strongest and stiffest of the three in absolute terms, but per unit mass titanium is far stronger, and the three have almost identical specific stiffness. This is why aircraft use aluminium and titanium alloys.
Because dictionaries are mutable, you can store a derived value back in each record with a loop. Later code can then use it like any other property.
materials = [ {"name": "Aluminium 6061", "density": 2.70, "yield_strength": 276}, {"name": "Magnesium AZ31", "density": 1.77, "yield_strength": 200},]for material in materials: material["specific_strength"] = round(material["yield_strength"] / material["density"], 1)print(materials[1])Materials selection usually starts by removing candidates that fail a hard requirement, then ranking the rest. A loop with an if, or a function that returns True or False, expresses the requirements clearly.
materials = [ {"name": "Aluminium 6061", "density": 2.70, "yield_strength": 276, "cost": 2.5}, {"name": "Titanium Ti-6Al-4V", "density": 4.43, "yield_strength": 880, "cost": 25.0}, {"name": "Steel AISI 4140", "density": 7.85, "yield_strength": 655, "cost": 1.2}, {"name": "Magnesium AZ31", "density": 1.77, "yield_strength": 200, "cost": 4.0}, {"name": "Copper C11000", "density": 8.96, "yield_strength": 70, "cost": 8.0},]def meets_requirements(material, max_density=5.0, min_strength=250): return material["density"] <= max_density and material["yield_strength"] >= min_strengthcandidates = []for material in materials: if meets_requirements(material): candidates.append(material)for material in candidates: print(material["name"])sorted() can't sort dictionaries on its own, because it doesn't know which property to compare. Its key= argument takes a function that is applied to each item to get the value to sort by. Notice that you pass the function's name without brackets: you are handing over the function itself for sorted() to call, not calling it yourself.
materials = [ {"name": "Aluminium 6061", "density": 2.70, "yield_strength": 276, "cost": 2.5}, {"name": "Titanium Ti-6Al-4V", "density": 4.43, "yield_strength": 880, "cost": 25.0}, {"name": "Steel AISI 4140", "density": 7.85, "yield_strength": 655, "cost": 1.2}, {"name": "Magnesium AZ31", "density": 1.77, "yield_strength": 200, "cost": 4.0},]def get_cost(material): return material["cost"]def strength_per_dollar(material): # MPa per (g/cm³) per ($/kg): higher is better value for a light, strong part return material["yield_strength"] / material["density"] / material["cost"]print("Cheapest first:")for material in sorted(materials, key=get_cost): print(f" {material['name']}: ${material['cost']}/kg")best = max(materials, key=strength_per_dollar)print("Best strength per dollar:", best["name"])max() and min() accept key= too. Titanium is the best performer, but on strength per dollar the cheap steel wins: ranking depends on which criterion you choose.
Beyond lists of dictionaries
Choose an answer and press Check answer. You can retry as often as you like.