codexpast-exam-of-the-year-2-of-the-earth-sciences-geology-course-of-the-university-of-oxford2025paper-3-q3-mohr-stress.py
past-exam-of-the-year-2-of-the-earth-sciences-geology-course-of-the-university-of-oxford/2025/paper-3-q3-mohr-stress.py
"""Original stress construction, Python 3.14.4 / NumPy 2.3.5 / Matplotlib 3.10.7.
Compression positive. theta measures the normal counterclockwise from sigma1;
positive shear uses clockwise tangent t=(sin(theta),-cos(theta)).
Writes an opaque basename PNG to the current working directory.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, Rectangle
def arrow(ax,a,b,color="black",lw=1.5):
ax.annotate("",xy=b,xytext=a,arrowprops={"arrowstyle":"->","color":color,"lw":lw})
def main():
plt.rcParams.update({"font.size":10})
fig,axes=plt.subplots(1,3,figsize=(12.2,5),layout="constrained")
fig.set_constrained_layout_pads(h_pad=.12,w_pad=.08)
ax=axes[0]
ax.add_patch(Rectangle((-1,-1),2,2,facecolor="#f2e5ca",edgecolor="0.3"))
for a,b in [((-1.5,0),(-1,0)),((1.5,0),(1,0)),((0,-1.5),(0,-1)),((0,1.5),(0,1))]: arrow(ax,a,b)
ax.text(-1.65,.17,r"$\sigma_1=80$",fontsize=9)
ax.text(.12,1.35,r"$\sigma_3=20$",fontsize=9)
n=np.array([np.cos(np.pi/6),np.sin(np.pi/6)]);t=np.array([n[1],-n[0]])
ax.plot([-t[0],t[0]],[-t[1],t[1]],"k",lw=2)
arrow(ax,(0,0),n,"#225f89");ax.text(*(.9*n+[0,.1]),r"$\mathbf{n}$")
arrow(ax,(0,0),.85*t,"#9a3d23");ax.text(*(.9*t+[.1,0]),r"$\mathbf{t}$")
ax.plot([0,1],[0,0],color="0.7",lw=.7)
ax.add_patch(Arc((0,0),.8,.8,theta1=0,theta2=30,color="#225f89"));ax.text(.5,.09,r"$\theta$")
# Physical force on the positive-normal face is minus compression-positive traction.
arrow(ax,(0,0),(-.8*n[0],-.2*n[1]),"#932b56",2)
ax.text(-1.52,-1.95,"On +n face: force/area = −σₙ n −τ t\nθ = 30°; axes are principal directions",fontsize=9)
ax.set(xlim=(-1.8,1.8),ylim=(-2.15,1.75),title="Rock and oriented internal plane")
ax.set_aspect("equal");ax.axis("off")
a=np.linspace(0,2*np.pi,721)
ax=axes[1];C,R=50,30
ax.plot(C+R*np.cos(a),R*np.sin(a),color="#225f89",lw=2)
ax.axhline(0,color="0.4",lw=.8);ax.axvline(0,color="0.4",lw=.8)
px,py=C+R/2,R*np.sqrt(3)/2
ax.plot([C,px],[0,py],color="#9a3d23");ax.plot(px,py,"o",color="#9a3d23")
ax.add_patch(Arc((C,0),25,25,theta1=0,theta2=60,color="#9a3d23"))
ax.text(63,7,r"$2\theta$",color="#9a3d23")
ax.annotate(r"$(\sigma_n,\tau)=(65,26)$",(px,py),xytext=(16,39),arrowprops={"arrowstyle":"->"},fontsize=9)
for x,label in [(20,r"$\sigma_3$"),(50,r"$C$"),(80,r"$\sigma_1$")]:ax.text(x,-6,label,ha="center")
ax.text(6,-37,r"$C=(\sigma_1+\sigma_3)/2$"+"\n"+r"$R=(\sigma_1-\sigma_3)/2$",fontsize=9)
ax.set(xlim=(-2,96),ylim=(-45,48),xlabel="Normal stress σₙ / MPa",ylabel="Signed shear τ / MPa",title="Mohr circle: θ becomes 2θ");ax.set_aspect("equal")
ax=axes[2]
ax.plot(C+R*np.cos(a),R*np.sin(a),color="#225f89",label="P = 0 MPa")
ax.plot(C-15+R*np.cos(a),R*np.sin(a),color="#9a3d23",label="P = 15 MPa")
s=np.linspace(0,90,100)
ax.plot(s,10+.6*s,"k--",label="|τ| = 10 + 0.6 σ′ₙ")
ax.plot(s,-10-.6*s,"k--")
ax.axhline(0,color="0.6",lw=.7);arrow(ax,(50,0),(35,0),"#9a3d23")
ax.text(23,-7,"−P",color="#9a3d23")
ax.set(xlim=(-3,96),ylim=(-48,55),xlabel="Effective normal stress σ′ₙ / MPa",ylabel="Signed shear τ / MPa",title="Fixed total stress; pressure coefficient = 1")
ax.legend(fontsize=8,loc="upper left");ax.set_aspect("equal")
fig.suptitle("Illustrative loading: compression positive; orange circle crosses strength (not a stable final state)",fontsize=11)
fig.savefig(Path(__file__).with_suffix(".png").name,dpi=130,facecolor="white")
if __name__=="__main__":main()