Let \(s \in \mathbb{C}\). Use SymPy to perform a partial fraction expansion on the following expression: \[ \frac{(s+2) (s + 10)}{s^4 + 8 s^3 + 117 s^2 + 610 s + 500}. \]
First, load SymPy and define the expression:
import sympy as sp
= sp.symbols("s", complex=True)
s = (s + 2)*(s + 10)/(s**4 + 8*s**3 + 117*s**2 + 610*s + 500) expr
The SymPy apart()
method can
be directly applied to find the partial fraction expansion:
expr.apart()
Let \(x, a_1, a_2, a_3, a_4 \in \mathbb{R}\). Use SymPy to combine the cosine and sine terms that share arguments into single sinusoids with phase shifts in the following expression: \[ a_1 \sin(x) + a_2 \cos(x) + a_3 \sin(2 x) + a_4 \cos(2 x) \]
First, load SymPy as follows:
import sympy as sp
Unfortunately, the SymPy trigsimp()
method doesn’t combine the
cosine and sine terms that share an argument. However, we can define a
functions trig_two_to_one()
for
performing the two-to-one conversion. The identities we will use are as
follows: $$\begin{align*}
a \cos u + b \sin u &= \sqrt{a^2 + b^2} \sin(u + \arctan(a/b)) \\
&= \sqrt{a^2 + b^2} \cos(u - \arctan(b/a)).
\end{align*}$$ We begin by defining two functions, each of which returns
a dictionary of replacement rules for sin(u) in the identities
above.
def _trig_two_to_one_sin_rule(a, b, u):
"""Replacement rule for sin(u) in a cos(u) + b sin(u) for
converting to a single sin term with a phase
The identity applied is:
a cos u + b sin u = sqrt(a^2 + b^2) sin(u + atan(a/b))
Returns: A dictionary for replacing sin(u)
"""
= a*sp.cos(u) + b*sp.sin(u) + \
identity - sp.sqrt(a**2 + b**2)*sp.sin(u + sp.atan(a/b))
= sp.solve(identity, [sp.sin(u)], dict=True)
sol return sol[0]
def _trig_two_to_one_cos_rule(a, b, u):
"""Replacement rule for sin(u) in a cos(u) + b sin(u) for
converting to a single cos term with a phase
The identity applied is:
a cos u + b sin u = sqrt(a^2 + b^2) cos(u - atan(b/a))
Returns: A dictionary for replacing sin(u)
"""
= a*sp.cos(u) + b*sp.sin(u) + \
identity - sp.sqrt(a**2 + b**2)*sp.cos(u - sp.atan(b/a))
= sp.solve(identity, [sp.sin(u)], dict=True)
sol return sol[0]
Now we can write a function to perform the trigonometric simplification, as follows:
def trig_two_to_one(expr: sp.Expr, to: str = "sin"):
"""Rewrites sin and cos terms that share arguments to single sin
or cos terms
Applies the following identity:
a cos u + b sin u = sqrt(a^2 + b^2) sin(u + atan(a/b))
or
a cos u + b sin u = sqrt(a^2 + b^2) cos(u - atan(b/a))
depending on the ``to`` argument.
Args:
expr: The symbolic expression containing sin(u) and cos(u)
to: Rewrite with "sin" (default) or "cos"
"""
= expr.simplify()
expr # Identify sin terms
= sp.Wild("w1", exclude=[1])
w1 = sp.Wild("w2")
w2 = expr.find(w1*sp.sin(w2))
sin_terms = {} # To be: {sin argument: sine amplitude}
sin_arg_amps for term in sin_terms:
= term.match(w1*sp.sin(w2))
arg_amp_rules = arg_amp_rules[w1]
sin_arg_amps[arg_amp_rules[w2]] # Identify cos terms
= expr.find(w1*sp.cos(w2))
cos_terms = {} # To be: {sin argument: sine amplitude}
cos_arg_amps for term in cos_terms:
= term.match(w1*sp.cos(w2))
arg_amp_rules = arg_amp_rules[w1]
cos_arg_amps[arg_amp_rules[w2]] # Replace with wildcard rule
for sin_arg, sin_amp in sin_arg_amps.items():
if to == "sin":
if sin_arg in cos_arg_amps.keys():
= cos_arg_amps[sin_arg]
cos_amp = _trig_two_to_one_sin_rule(
sin_rule
cos_amp, sin_amp, sin_arg
)= expr.subs(sin_rule)
expr elif to == "cos":
if sin_arg in cos_arg_amps.keys():
= cos_arg_amps[sin_arg]
cos_amp = _trig_two_to_one_cos_rule(
cos_rule
cos_amp, sin_amp, sin_arg
)= expr.subs(cos_rule)
expr return expr.simplify()
The expression of interest in the problem is defined here:
= sp.symbols("x, a1, a2, a3, a4", real=True)
x, a1, a2, a3, a4 = a1*sp.sin(x) + a2*sp.cos(x) + a3*sp.sin(2*x) + a4*sp.cos(2*x) expr
Apply the function to the expression, rewriting in terms of sine (first) and cosine (second):
="sin")
trig_two_to_one(expr, to="cos") trig_two_to_one(expr, to
Consider the following equation, where \(x \in \mathbb{C}\) and \(a, b, c \in \mathbb{R_+}\), \[ a x^2 + b x + \frac{c}{x} + b^2 = 0. \] Use SymPy to solve for \(x\).
First, load SymPy as follows:
import sympy as sp
Now define the symbolic variables and the equation:
= sp.symbols("x", complex=True)
x = sp.symbols("a, b, c", positive=True)
a, b, c = a*x**2 + b*x + c/x + b**2 # == 0 eq
Let’s try sp.solve()
, as
follows:
= sp.solve(eq, x, dict=True)
sol print(sol)
[{x: -(-3*b**2/a + b**2/a**2)/(3*(sqrt(-4*(-3*b**2/a + b**2/a**2)**3 + (27*c/a - 9*b**3/a**2 + 2*b**3/a**3)**2)/2 + 27*c/(2*a) - 9*b**3/(2*a**2) + b**3/a**3)**(1/3)) - (sqrt(-4*(-3*b**2/a + b**2/a**2)**3 + (27*c/a - 9*b**3/a**2 + 2*b**3/a**3)**2)/2 + 27*c/(2*a) - 9*b**3/(2*a**2) + b**3/a**3)**(1/3)/3 - b/(3*a)}, {x: -(-3*b**2/a + b**2/a**2)/(3*(-1/2 - sqrt(3)*I/2)*(sqrt(-4*(-3*b**2/a + b**2/a**2)**3 + (27*c/a - 9*b**3/a**2 + 2*b**3/a**3)**2)/2 + 27*c/(2*a) - 9*b**3/(2*a**2) + b**3/a**3)**(1/3)) - (-1/2 - sqrt(3)*I/2)*(sqrt(-4*(-3*b**2/a + b**2/a**2)**3 + (27*c/a - 9*b**3/a**2 + 2*b**3/a**3)**2)/2 + 27*c/(2*a) - 9*b**3/(2*a**2) + b**3/a**3)**(1/3)/3 - b/(3*a)}, {x: -(-3*b**2/a + b**2/a**2)/(3*(-1/2 + sqrt(3)*I/2)*(sqrt(-4*(-3*b**2/a + b**2/a**2)**3 + (27*c/a - 9*b**3/a**2 + 2*b**3/a**3)**2)/2 + 27*c/(2*a) - 9*b**3/(2*a**2) + b**3/a**3)**(1/3)) - (-1/2 + sqrt(3)*I/2)*(sqrt(-4*(-3*b**2/a + b**2/a**2)**3 + (27*c/a - 9*b**3/a**2 + 2*b**3/a**3)**2)/2 + 27*c/(2*a) - 9*b**3/(2*a**2) + b**3/a**3)**(1/3)/3 - b/(3*a)}]
This is a cubic solution, so it is unwieldy.
Let \(w, x, y, z \in \mathbb{R}\). Consider the following system of equations: $$\begin{alignat*}{3} 8 w &{}-{} 6 x &{}+{} 5 y &{}+{} 4 z &{}={} -20 \\ \phantom{3 w} &{}\mathbin{\phantom{+}}{} \phantom{4 x} &{}\mathbin{\phantom{+}}{} 2 y &{}-{} 2 z &{}={} 10 \\ 2 w &{}-{} \phantom{3}x &{}+{} 4 y &{}+{} \phantom{3}z &{}={} 0\\ w &{}+{} 4 x &{}-{} 2 y &{}+{} 8 z &{}={} 4. \end{alignat*}$$ Use SymPy to solve the system for \(w\), \(x\), \(y\), and \(z\).
Load SymPy as follows:
import sympy as sp
Now define the symbolic variables:
= sp.symbols("w, x, y, z", complex=True) w, x, y, z
Define the set of equations:
= [
S 8*w - 6*x + 5*y + 4*z + 20, # == 0
2*y - 2*z - 10, # == 0
2*w - x + 4*y + z, # == 0
+ 4*x - 2*y + 8*z - 4, # == 0
w ]
Let’s try sp.solve()
, as
follows:
= sp.solve(S, [w, x, y, z], dict=True)
sol print(sol)
[{w: 564/85, x: 773/85, y: 14/85, z: -411/85}]
Consider the truss shown in fig. ¿fig:truss-03?. Use a static analysis and the method of joints to develop a solution for the force in each member \(F_{AC}\), \(F_{AD}\), etc., and the reaction forces using the sign convention that tension is positive and compression is negative. The forces should be expressed in terms of the applied force \(\vec{f}_D\) and the dimensions \(w\) and \(h\) only. Write a program that solves for the forces symbolically and answers the following questions:
- Which members are in tension?
- Which members are in compression?
- Are there any members with \(0\) nominal force? If so, which?
- Which member (or members) has (or have) the maximum compression?
- Which member (or members) has (or have) the maximum tension?
#### Import Packages {-}
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
Static Analysis
Using the method of joints, we proceed through the joints, summing forces in the \(x\)- and \(y\)-directions. We will assume all members are in tension, and their sign will be positive if this is the case and negative, otherwise. Beginning with joint A, which includes two reaction forces \(R_{A_x}\) and \(R_{A_y}\) from the support, $$\begin{align} \Sigma F_x &= 0; & R_{A_x} + F_{AD} + F_{AC} \cos\theta &=0 \\ \Sigma F_y &= 0; & R_{A_y} - F_{AC} \sin\theta &= 0. \end{align}$$ The angle \(\theta\) is known in terms of the dimensions \(w\) and \(h\) as \[\theta = \arctan\frac{h}{w}.\] These equations can be encode symbolically as follows:
= sp.symbols(
RAx, RAy, FAC, FAD, theta "RAx, RAy, FAC, FAD, theta", real=True
)= sp.symbols("h, w", positive=True)
h, w = RAx + FAD + FAC * sp.cos(theta)
eqAx = RAy - FAC*sp.sin(theta)
eqAy = sp.atan(h/w) theta_wh
Proceeding to joint B, which also has two reaction forces \(R_{B_x}\) and \(R_{B_y}\), $$\begin{align} \Sigma F_x &= 0; & R_{B_x} + F_{BC} &= 0 \\ \Sigma F_y &= 0; & R_{B_y} &= 0. \end{align}$$ Encoding these equations,
= sp.symbols("RBx, RBy, FBC", real=True)
RBx, RBy, FBC = RBx + FBC
eqBx = RBy eqBy
For joint C, $$\begin{align} \Sigma F_x &= 0; & -F_{BC} - F_{AC} \cos\theta &= 0 \\ \Sigma F_y &= 0; & F_{CD} + F_{AC} \sin\theta &= 0. \end{align}$$ Encoding these equations,
= sp.symbols("FCD", real=True)
FCD = -FBC - FAC * sp.cos(theta)
eqCx = FCD + FAC * sp.sin(theta) eqCy
For joint D, there is an applied load \(\vec{f_D}\), so the analysis proceeds as follows: $$\begin{align} \Sigma F_x &= 0; & -F_{AD} - f_D \cos\frac{\pi}{4} &= 0 \\ \Sigma F_y &= 0; & -F_{CD} - f_D \sin\frac{\pi}{4} &= 0. \end{align}$$ Encoding these equations,
= sp.symbols("fD", positive=True)
fD = -FAD - fD * sp.cos(sp.pi/4)
eqDx = -FCD - fD * sp.sin(sp.pi/4) eqDy
In total, we have 8 force equations and 8 unknown forces (4 member forces and 4 reaction forces). Let’s construct the system and solve it for the unkown forces, as follows:
= [
S_forces
eqAx, eqAy, eqBx, eqBy, eqCx, eqCy, eqDx, eqDy# 8 force equations
] = [FAC, FAD, FBC, FCD] # 5 member forces
member_forces = [RAx, RAy, RBx, RBy] # 3 reaction forces
reaction_forces = member_forces + reaction_forces # 8 unkown forces
forces_unknown = sp.solve(S_forces, forces_unknown, dict=True); sol_forces sol_forces
[{FAC: sqrt(2)*fD/(2*sin(theta)),
FAD: -sqrt(2)*fD/2,
FBC: -sqrt(2)*fD*cos(theta)/(2*sin(theta)),
FCD: -sqrt(2)*fD/2,
RAx: sqrt(2)*fD/2 - sqrt(2)*fD*cos(theta)/(2*sin(theta)),
RAy: sqrt(2)*fD/2,
RBx: sqrt(2)*fD*cos(theta)/(2*sin(theta)),
RBy: 0}]
This solution is in terms of \(f_D\)
and \(\theta\). Because \(w\) and \(h\) are our design parameters, let’s
substitute theta_wh
such that our
solution is rewritten in terms of \(f_D\), \(w\), and \(h\). Create a dict
of
solutions as follows:
= {} # Initialize
forces_wh for force in forces_unknown:
= force.subs(
force_wh 0]
sol_forces[
).subs(
theta, theta_wh
).simplify()= force_wh
forces_wh[force] print(f"{force} = {force_wh}")
FAC = fD*sqrt(2*h**2 + 2*w**2)/(2*h)
FAD = -sqrt(2)*fD/2
FBC = -sqrt(2)*fD*w/(2*h)
FCD = -sqrt(2)*fD/2
RAx = sqrt(2)*fD*(h - w)/(2*h)
RAy = sqrt(2)*fD/2
RBx = sqrt(2)*fD*w/(2*h)
RBy = 0
Identify the member forces in tension and compression as follows:
= [] # Initialize list
tension = [] # Initialize list
compression for force in member_forces:
if forces_wh[force] > 0:
tension.append(force)print(f"{force} is in tension")
elif forces_wh[force] < 0:
compression.append(force)print(f"{force} is in compression")
else:
print(f"{force} is a zero-force member")
FAC is in tension
FAD is in compression
FBC is in compression
FCD is in compression
Therefore, there are no zero-force members. The maximum compression depends on the ratio \(w/h\). If \(w/h = 1\), all three members in compression have the same compression of magnitude \[ \frac{\sqrt{2}}{2} f_D. \] This is also the maximum compression if \(w/h < 1\). If \(w/h > 1\), the maximum compression is of magnitude \[ \frac{\sqrt{2} w}{2 h} f_D. \]
You are designing the truss structure shown in fig. ¿fig:truss-02?, which is to support the hanging of an external load \(\bm{f}_C = -f_C \bm{\hat{j}}\), where \(f_C > 0\). Your organization plans to offer customers the following options:
- Any width (i.e., \(2 w\))
- A selection of maximum load magnitudes \(L = f_C / \alpha \in \Gamma\), where \(\Gamma = \{1, 2, 4, 8, 16\}\) kN, and where \(\alpha\) is the factor of safety
As the designer, you are to develop a design curve for the dimension \(h\) versus half-width \(w\) for each maximum load \(L \in \Gamma\), under the following design constraints:
- Minimize the dimension \(h\)
- The tension in all members is no more than a given \(T\)
- The compression in all members is no more than a given \(C\)
- The magnitude of the support force at pin A is no more than a given \(P_A\)
- The magnitude of the support force at pin D is no more than a given \(P_D\)
Use a static analysis and the method of joints to develop a solution for the force in each member \(F_{AB}\), \(F_{AC}\), etc., and the reaction forces using the sign convention that tension is positive and compression is negative. Create a Python function that returns \(h\) as a function of \(w\) for a given set of design parameters \(\{T, C, P_A, P_D, \alpha, L\}\). Use the function to create a design curve \(h\) versus \(2 w\) for each \(L \in \Gamma\), maximum tension \(T = 81\) kN, maximum compression \(C = 81\) kN, maximum support A load \(P_A = 50\) kN, maximum support D load \(P_D = 50\) kN, and a factor of safety of \(\alpha = 5\).
#### Import Packages {-}
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
Static Analysis
Using the method of joints, we proceed through the joints, summing forces in the \(x\)- and \(y\)-directions. We will assume all members are in tension, and their sign will be positive if this is the case and negative, otherwise. Beginning with joint A, which includes one reaction force \(R_{A}\) from the support, $$\begin{align} \Sigma F_x &= 0; & F_{AC} + F_{AB} \cos\theta &=0 \\ \Sigma F_y &= 0; & R_A + F_{AB} \sin\theta &= 0. \end{align}$$ The angle \(\theta\) is known in terms of the dimensions \(w\) and \(h\) as \[\theta = \arctan\frac{h}{w}.\] These equations can be encode symbolically as follows:
= sp.symbols(
RA, FAB, FAC, theta"RA, FAB, FAC, theta", real=True
)= sp.symbols("h, w", positive=True)
h, w = FAC + FAB*sp.cos(theta)
eqAx = RA + FAB*sp.sin(theta)
eqAy = sp.atan(h/w) theta_wh
Proceeding to joint B, $$\begin{align} \Sigma F_x &= 0; & -F_{AB} \cos\theta + F_{BD} \cos\theta &= 0 \\ \Sigma F_y &= 0; & -F_{AB} \sin\theta - F_{BD} \sin\theta - F_{BC} &= 0. \end{align}$$ Encoding these equations,
= sp.symbols("FBD, FBC", real=True)
FBD, FBC = -FAB*sp.cos(theta) + FBD*sp.cos(theta)
eqBx = -FAB*sp.sin(theta) - FBD*sp.sin(theta) - FBC eqBy
For joint C, there is an externally applied force \(\bm{f}_C\), so the analysis proceeds as follows: $$\begin{align} \Sigma F_x &= 0; & -F_{AC} + F_{CD} &= 0 \\ \Sigma F_y &= 0; & F_{BC} - f_C &= 0. \end{align}$$ Encoding these equations,
= sp.symbols("FCD", real=True)
FCD = sp.symbols("fC", positive=True)
fC = -FAC + FCD
eqCx = FBC - fC eqCy
For joint D, the pinned reaction forces \(R_{Dx}\) and \(R_{Dy}\) are present, so the analysis proceeds as follows: $$\begin{align} \Sigma F_x &= 0; & -F_{CD} - F_{BD} \cos\theta + R_{Dx} &= 0 \\ \Sigma F_y &= 0; & F_{BD} \sin\theta + R_{Dy} &= 0. \end{align}$$ Encoding these equations,
= sp.symbols("RDx, RDy", real=True)
RDx, RDy = -FCD - FBD*sp.cos(theta) + RDx
eqDx = FBD*sp.sin(theta) + RDy eqDy
In total, we have 8 force equations and 8 unknown forces (5 member forces and 3 reaction forces). Let’s construct the system and solve it for the unkown forces, as follows:
= [
S_forces
eqAx, eqAy, eqBx, eqBy, eqCx, eqCy, eqDx, eqDy# 8 force equations
] = [FAB, FAC, FBC, FBD, FCD] # 5 member forces
member_forces = [RA, RDx, RDy] # 3 reaction forces
reaction_forces = member_forces + reaction_forces # 8 unkown forces
forces_unknown = sp.solve(S_forces, forces_unknown, dict=True); sol_forces sol_forces
[{FAB: -fC/(2*sin(theta)),
FAC: fC*cos(theta)/(2*sin(theta)),
FBC: fC,
FBD: -fC/(2*sin(theta)),
FCD: fC*cos(theta)/(2*sin(theta)),
RA: fC/2,
RDx: 0,
RDy: fC/2}]
This solution is in terms of \(f_C\)
and \(\theta\). Because \(w\) and \(h\) are our design parameters, let’s
substitute eqtheta
such that our
solution is rewritten in terms of \(f_F\), \(w\), and \(h\). Create a list of solutions as
follows:
= {} # Initialize
forces_wh for force in forces_unknown:
= force.subs(
force_wh 0]
sol_forces[
).subs(
theta, theta_wh
).simplify()= force_wh
forces_wh[force] print(f"{force} = {force_wh}")
FAB = -fC*sqrt(h**2 + w**2)/(2*h)
FAC = fC*w/(2*h)
FBC = fC
FBD = -fC*sqrt(h**2 + w**2)/(2*h)
FCD = fC*w/(2*h)
RA = fC/2
RDx = 0
RDy = fC/2
Define the constraints as follows:
= sp.symbols("C, T, PA, PD", positive=True)
C, T, PA, PD = {"Tension": T, "Compression": C}
member_constraints = {
reaction_constraints 0),
PA: (RA,
PD: (RDx, RDy)# Max magnitude: (x force, y force) }
Define a function that encodes the constraints as a list of expressions that must be nonnegative.
def get_force_constraints(
dict,
forces: list,
member_forces: dict,
member_constraints: dict,
reaction_constraints:
):"""Returns a list of expressions that must be nonnegative
Args:
- forces: Force solutions {force symbol: solution}
- member_forces: List of member symbols [FAB, FAC ...]
- member_constraints:
{"Tension": max tension, "Compression": max compression}
- reaction_constraints:
{max force symbol: (max x force, max y force)}
"""
= []
force_constraints # Append member constraints
for f_name, f_value in forces.items():
if f_name in member_forces:
if f_value > 0: # Tension
force_constraints.append("Tension"] - f_value
member_constraints[
)elif f_value < 0: # Compression
force_constraints.append("Compression"] + f_value
member_constraints[
)# Append reaction constraints
for constraint, pair in reaction_constraints.items():
force_constraints.append(- sp.sqrt(pair[0]**2 + pair[1]**2).subs(forces_wh)
constraint
)return force_constraints
Now apply the function:
= get_force_constraints(
constraints
forces_wh, member_forces, member_constraints, reaction_constraints
)print(constraints)
[C - fC*sqrt(h**2 + w**2)/(2*h), T - fC*w/(2*h), T - fC, C - fC*sqrt(h**2 + w**2)/(2*h), T - fC*w/(2*h), PA - fC/2, PD - fC/2]
At this point, we can solve for \(h\) in each expression that includes \(h\). Because these constraints are to be nonnegative, solving with equality gives the minimum \(h_\text{min}\). Expressions that don’t include \(h\) must still be checked. We can use this opportunity to sort the constraint expressions into those that involve \(h\) and those that do not.
= []
h_mins = []
constraints_to_check for constraint in constraints:
if h in constraint.free_symbols:
# Solve for h
= sp.solve(constraint, h)
h_min_sol for h_min in h_min_sol:
h_mins.append(h_min)else:
constraints_to_check.append(constraint)
Inspect the h_mins
and constraints_to_check
:
print(f"Min h solutions: {h_mins}")
print(f"Constraints to be checked: {constraints_to_check}")
Min h solutions: [fC*w*sqrt(1/(2*C - fC))/sqrt(2*C + fC), fC*w/(2*T), fC*w*sqrt(1/(2*C - fC))/sqrt(2*C + fC), fC*w/(2*T)]
Constraints to be checked: [T - fC, PA - fC/2, PD - fC/2]
Finally, define a function to design the truss:
def truss_designer(
list,
h_mins: list,
constraints_to_check: dict,
design_params:
):"""Returns an expression for h(w) using the design parameters provided
"""
= sp.symbols("L", positive=True)
L = sp.symbols("alpha", positive=True)
alpha for constraint in constraints_to_check:
= constraint.subs(fC, L*alpha).subs(design_params)
constraint_ if constraint_ < 0:
raise Exception(
f"Design failed for constraint: {constraint} < 0"
)= []
h_mins_ for h_min in h_mins:
*alpha).subs(design_params))
h_mins_.append(h_min.subs(fC, Lreturn max(h_mins_) # Maximum h_min
Define the given design parameters in a dictionary:
= sp.symbols("alpha", positive=True)
alpha = {
design_parameters 81e3, C: 81e3, PA: 50e3, PD: 50e3, alpha: 5,
T: # Forces in N
} = [1e3, 2e3, 4e3, 8e3, 16e3] Gamma
Loop through the L
loads, running
truss_designer()
:
= []
h_mins_w = sp.symbols("L", positive=True)
L for L_ in Gamma:
= L_
design_parameters[L] = truss_designer(
h_min
h_mins, constraints_to_check, design_parameters
)
h_mins_w.append(h_min)print(h_min)
0.0308789086390917*w
0.0618463369994117*w
0.124408521678431*w
0.254802914503365*w
0.56790458868584*w
Convert these symbolic expressions into numerically evaluable function, as follows:
= []
h_min_funs for h_min in h_mins_w:
h_min_funs.append(=np)
sp.lambdify([w], h_min, modules )
Plot the design curves as follows:
= np.linspace(1, 20, 101)
w_ = plt.subplots()
fig, ax, for ih, h_min_fun in enumerate(h_min_funs):
2*w_, h_min_fun(w_), label=f"L = {Gamma[ih]}")
ax.semilogy(f"width $2w$ (m)")
ax.set_xlabel(f"height $h$ (m)")
ax.set_ylabel(
ax.grid()
ax.legend() plt.show()
Consider an LTI system modeled by the state equation of the state-space model, eq. ¿eq:state-space-model-state?. A steady state of a system is defined as the state vector \(\bm{x}(t)\) after the effects of initial conditions have become relatively small. For a constant input \(\bm{u}(t) = \overline{\bm{u}}\), the constant state \(\overline{\bm{x}}\) toward which the system’s response decays can be found by setting the time derivative vector \(\bm{x}'(t) = \bm{0}\).
Write a Python function steady_state()
that accepts the
following arguments:
A
: A symbolic matrix representing \(A\)B
: A symbolic matrix representing \(B\)u_const
: A symbolic vector representing \(\overline{\bm{u}}\)
The function should return x_const
, a symbolic vector representing
\(\overline{\bm{x}}\).
The steady-state output converges to \(\overline{\bm{y}}\) the corresponding
output equation of the state-space model,
eq. ¿eq:state-space-model-output?. Write a second
Python function steady_output()
that accepts the following arguments:
C
: A symbolic matrix representing \(C\)D
: A symbolic matrix representing \(D\)u_const
: A symbolic vector representing \(\overline{\bm{u}}\)x_const
: A symbolic vector representing \(\overline{\bm{x}}\)
This function should return y_const
, a symbolic vector representing
\(\overline{\bm{y}}\).
Apply steady_state()
and steady_output()
to the state-space
model of the circuit shown in fig. ¿fig:w5?, which
includes a resistor with resistance \(R\), an inductor with inductance \(L\), and capacitor with capacitance \(C\). The LTI system is represented by
eq. ¿eq:state-space-model? with state, input, and
output vectors
\[
\bm{x}(t) = \begin{bmatrix} v_C(t) \\ i_L(t) \end{bmatrix},\
\bm{u}(t) = \begin{bmatrix} V_S \end{bmatrix},\
\bm{y}(t) = \begin{bmatrix} v_C(t) \\ v_L(t) \end{bmatrix}
\] and the following matrices: \[
A = \begin{bmatrix} 0 & 1/C \\ -1/L & -R/L \end{bmatrix},\
B = \begin{bmatrix} 0 \\ 1/L \end{bmatrix},\
C = \begin{bmatrix} 1 & 0 \\ -1 & -R \end{bmatrix},\
D = \begin{bmatrix} 0 \\ 1 \end{bmatrix}.
\] Furthermore, let the constant input vector be \[
\overline{\bm{u}} = \begin{bmatrix} \overline{V_S} \end{bmatrix},
\] for constant \(\overline{V_S}\).
A constant steady-state, \(\bm{x}' =
\bm{0}\) implies, from the state
eq. ¿eq:state-space-model-state?, $$\begin{align}
\bm{0} &= A \overline{\bm{x}} + B \overline{\bm{u}} \Rightarrow \\
\overline{\bm{x}} &= -A^{-1} B \overline{\bm{u}}.
\end{align}$$ We are now ready to define steady_state()
as follows:
def steady_state(A, B, u_const):
"""Returns the symbolic constant steady state vector"""
= sp.Matrix(A) # In case A isn't symbolic
A = sp.Matrix(B) # In case B isn't symbolic
B = sp.Matrix(u_const) # In case u_const isn't symbolic
u_const = -A**-1 * B * u_const
x_const return x_const
The state-space output equation
eq. ¿eq:state-space-model-output? is already solved for
the output, so we are ready to write steady_output()
as follows:
def steady_output(C, D, u_const, x_const):
"""Returns the symbolic constant steady-state output vector"""
= sp.Matrix(C) # In case C isn't symbolic
C = sp.Matrix(D) # In case D isn't symbolic
D = sp.Matrix(u_const) # In case u_const isn't symbolic
u_const = sp.Matrix(x_const) # In case x_const isn't symbolic
x_const = C*x_const + D*u_const
y_const return y_const
Apply these functions to the given state-space model. First, define the symbolic variables as follows:
= sp.symbols("R, L, C1", positive=True)
R, L, C1 = sp.symbols("VS_", real=True) # Constant voltage source input VS_
Now define the system and the constant input as follows:
= sp.Matrix([[0, 1/C1], [-1/L, -R/L]]) # $A$
A = sp.Matrix([[0], [1/L]]) # $B$
B = sp.Matrix([[1, 0], [-1, -R]]) # $C$
C = sp.Matrix([[0], [1]]) # $D$
D = sp.Matrix([[VS_]]) # $\overline{\bm{u}}$ u_const
Find the constant steady state \(\overline{\bm{x}}\) as follows:
= steady_state(A, B, u_const)
x_const print(x_const)
Find the constant steady-state output \(\overline{\bm{y}}\) as follows:
= steady_output(C, D, u_const, x_const)
y_const print(y_const)
Consider the electromechanical state-space model described in example. For a given set of parameters, input voltage, and initial conditions, the following vector-valued functions have been derived: \[ \bm{F} = \begin{bmatrix} \int_0^t v_R(t)\ dt \\ \int_0^t v_L(t)\ dt \\ \int_0^t \Omega_B(t)\ dt \\ \int_0^t \Omega_J(t)\ dt \\ \end{bmatrix} = \begin{bmatrix} \exp(-t) \\ \exp(-t) \\ 1 - \exp(-t) \\ 1 - \exp(-t) \end{bmatrix}, \quad \bm{G} = \begin{bmatrix} \int_0^t i_R(t)\ dt \\ \int_0^t i_L(t)\ dt \\ \int_0^t T_B(t)\ dt \\ \int_0^t T_J(t)\ dt \\ \end{bmatrix} = \begin{bmatrix} \exp(-t) \\ \exp(-t) \\ 1 - \exp(-t) \\ \exp(-t) \end{bmatrix} \] The instantaneous power lossed or stored by each element is given by the following vector of products: \[ \bm{\mathcal{P}}(t) = \begin{bmatrix} v_R(t) i_R(t) \\ v_L(t) i_L(t) \\ \Omega_B(t) T_B(t) \\ \Omega_J(t) T_J(t) \end{bmatrix}. \] The energy \(\mathcal{E}(t)\) of the elements, then, is \[ \bm{\mathcal{E}}(t) = \int_0^t \bm{\mathcal{P}}(t) dt. \]
Write a program that satisfies the following requirements:
- It defines a function
power(F, G)
that returns the symbolic power vector \(\bm{\mathcal{P}}(t)\) from any inputs \(\bm{F}\) and \(\bm{G}\) - It defines a function
energy(F, G)
that returns the symbolic energy \(\bm{\mathcal{E}}(t)\) from any inputs \(\bm{F}\) and \(\bm{G}\) (energy()
should callpower()
) - It tests the
energy()
on the specific \(\bm{F}\) and \(\bm{G}\) given above
The formula for the power of each element is given, so we are ready
to define power()
as follows:
def power(F, G):
"""Returns the power for vectors F and G"""
= sp.Matrix(F) # In case F isn't symbolic
F = sp.Matrix(G) # In case G isn't symbolic
G = F.multiply_elementwise(G)
P # Alternative using a for loop:
# P = sp.zeros(*F.shape) # Initialize
# for i, Fi in enumerate(F):
# P[i] = Fi * G[i]
return P
The formula for the energy stored or dissipated by each element is
given, so we are ready to write energy()
as follows:
def energy(F, G):
"""Returns the energy stored for vectors F and G"""
= power(F, G)
P = sp.integrate(P, (t, 0, t))
E return E
Apply these functions to the given \(\bm{F}\) and \(\bm{G}\). First, define \(\bm{F}\) and \(\bm{G}\) as follows:
= sp.symbols("t", real=True)
t = sp.Matrix([
F -t)],
[sp.exp(-t)],
[sp.exp(1 - sp.exp(-t)],
[1 - sp.exp(-t)]
[
])= sp.Matrix([
G -t)],
[sp.exp(-t)],
[sp.exp(1 - sp.exp(-t)],
[-t)]
[sp.exp( ])
Now compute the energy:
= energy(F, G).simplify()
E print(E)
For the circuit and state-space model given in problem 4.7, use SymPy to solve for \(\vec{x}(t)\) and \(\vec{y}(t)\) given the following:
- A constant input voltage \(V_S(t) = \overline{V_S}\)
- Initial condition \(\vec{x}(0) = \vec{0}\)
Substitute the following parameters into the solution for \(\vec{y}(t)\) and create numerically evaluable functions of time for each variable in \(\vec{y}(t)\): \[ R = 50\text{ $\Omega$, } L = 10\cdot 10^{-6}\text{ H, } C = 1\cdot 10^{-9}\text{ F, } \overline{V_S} = 10\text{ V}. \]
Plot the outputs in \(\vec{y}(t)\) as functions of time, making sure to choose a range of time over which the response is best presented. Hint: An appropriate amount of time is on the scale of microseconds.
Load packages:
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
Define Classes
We begin by defining the parameters and functions of time as SymPy symbolic variables and unspecified functions as follows:
= sp.symbols("R, L, C", positive=True)
R, L, C = sp.symbols(
v_C, i_L, v_L, V_S "v_C, i_L, v_L, V_S", cls=sp.Function, real=True
# $v_C, i_L, V_S$
) = sp.symbols("t", real=True) t
Now we can form the symbolic matrices and vectors:
= sp.Matrix([[0, 1/C], [-1/L, -R/L]]) # $\mat{A}$
A_ = sp.Matrix([[0], [1/L]]) # $\mat{B}$
B_ = sp.Matrix([[1, 0], [-1, -R]]) # $\mat{C}$
C_ = sp.Matrix([[0], [1]]) # $\mat{D}$
D_ = sp.Matrix([[v_C(t)], [i_L(t)]]) # $\vec{x}$
x = sp.Matrix([[V_S(t)]]) # $\vec{u}$
u = sp.Matrix([[v_C(t)], [v_L(t)]]) # $\vec{y}$ y
The input and initial conditions can be encoded as follows:
= {V_S(t): 10}
u_subs = {v_C(0): 0, i_L(0): 0} ics
The set of first-order ODEs comprising the state equation can be defined as follows:
= x.diff(t) - A_*x - B_*u
odes print(odes)
= sp.dsolve(list(odes.subs(u_subs)), list(x), ics=ics) x_sol
The symbolic solutions for \(\bm{x}(t)\) are lengthy expressions, so we don’t print them here. Now we can compute the output \(\vec{y}(t)\) from eq. ¿eq:state-space-model-output? as follows:
= {} # Initialize
x_sol_dict for eq in x_sol:
= eq.rhs # Make a dict of solutions for subs
x_sol_dict[eq.lhs] = (C_*x + D_*u).subs(x_sol_dict) # Subs into output equation
y_sol
# We will graph the output for the following set of parameters:
= {
params 50, # (Ohms)
R: 10e-6, # (H)
L: 1e-9, # (F)
C: }
Create a numerically evaluable version of each function as follows:
= sp.lambdify(
v_C_ 0].subs(params).subs(u_subs), modules="numpy"
t, y_sol[
)= sp.lambdify(
v_L_ 1].subs(params).subs(u_subs), modules="numpy"
t, y_sol[ )
Graph each solution as follows:
= np.linspace(0, 0.000002, 201)
t_ = plt.subplots(2, sharex=True)
fig, axs 0].plot(t_, v_C_(t_))
axs[1].plot(t_, v_L_(t_))
axs[1].set_xlabel("Time (s)")
axs[0].set_ylabel("$v_C(t)$ (rad/s)")
axs[1].set_ylabel("$v_L(t)$ (A)")
axs[ plt.show()
The output equation is trivial in this case, yielding only the state variable \(\Omega_J(t)\), for which we have already solved. Therefore, we have completed the analysis.
Online Resources for Section 4.8
No online resources.