Principio dei Lavori virtuali¶

da PowerShell -- jupyter nbconvert --to html notebook.ipynb

Trave rigida incernierata a sinistra, soggetta a peso proprio¶

  • Spostamento virtuale del baricentro $\large \delta_{yG}=\frac{L}{2}cos\theta\, d\theta$
  • Lavori virtuali, cioè $\large \delta L=0 \quad$ per qualunque $\quad \delta \theta \quad \rightarrow \quad m\, g\frac{L}{2}cos\theta=0 \quad$ che non ha soluzione fisica

risulta in equilibrio (instabile) solo quando la trave è in verticale¶

In [15]:
import numpy as np

# parametri fisici
m = 1.0     # kg
g = 9.81    # m/s^2
L = 1.0     # m

def virtual_work(theta, delta_theta):
    """
    Lavoro virtuale del peso per una trave incernierata.
    """
    delta_yG = (L / 2) * np.cos(theta) * delta_theta
    return -m * g * delta_yG

def equilibrium_condition(theta):
    """
    Coefficiente di delta_theta nel lavoro virtuale.
    Deve essere zero per l'equilibrio.
    """
    return m * g * (L / 2) * np.cos(theta)

# test su vari angoli
angles = np.linspace(-np.pi, np.pi, 9)

print("theta [deg] | coefficiente lavoro virtuale")
print("-------------------------------------------")
for th in angles:
    print(f"{th*180/np.pi:8.2f} | {equilibrium_condition(th):8.2f}")
theta [deg] | coefficiente lavoro virtuale
-------------------------------------------
 -180.00 |    -4.91
 -135.00 |    -3.47
  -90.00 |     0.00
  -45.00 |     3.47
    0.00 |     4.91
   45.00 |     3.47
   90.00 |     0.00
  135.00 |    -3.47
  180.00 |    -4.91

Stessa trave, ma con una molla a destra¶

  • $\large F_{molla}=-kL\, sin\theta$
  • mi sposto di $\delta\theta$
  • $\large \delta L_{peso}=-mg\frac{L}{2}cos\theta\, d\theta$
  • $\large \delta L_{molla} = F_{molla}\delta_{y\, molla}=-k\, Lsin\theta\, Lcos\theta\, d\theta=-k\, L^2\, sin\theta\, cos\theta\, \delta\theta$
  • $\large \delta L_{tot}=\delta L_{peso}+\delta L_{molla}=\left( -mg\frac{L}{2}cos\theta-k\, L^2\, sin\theta\, cos\theta \right)\, \delta\theta =0 \quad$ per qualunque $\delta\theta$
  • $\large cos\theta\, \left( m\, g\, \frac{L}{2}+k\, L^2sin\theta \right)=0 \quad \rightarrow \quad sin\theta=\frac{m\, g}{2\, k\, L}$
In [ ]:
 
In [19]:
import numpy as np

# Parametri fisici
m = 1.0      # massa della trave [kg]
g = 9.81     # gravità [m/s^2]
L = 1.0      # lunghezza della trave [m]
k = 50.0     # costante molla [N/m]

def virtual_work(theta, delta_theta):
    """
    Lavoro virtuale totale della trave con molla al punto destro
    """
    # lavoro del peso al baricentro
    delta_L_peso = - m * g * (L / 2) * np.cos(theta) * delta_theta
    
    # lavoro della molla al punto destro (verticale)
    delta_L_molla = - k * L * np.sin(theta) * L * np.cos(theta) * delta_theta
    
    # lavoro totale
    return delta_L_peso + delta_L_molla

def equilibrium_condition(theta):
    """
    Coefficiente di delta_theta nel lavoro virtuale totale.
    Equilibrio: coefficiente = 0
    """
    return m * g * (L / 2) * np.cos(theta) + k * L**2 * np.sin(theta) * np.cos(theta)

# Risoluzione numerica dell'equilibrio
from scipy.optimize import fsolve

# guess iniziale piccola
theta_guess = 0.0
theta_eq = fsolve(equilibrium_condition, theta_guess)[0]

print(f"Angolo di equilibrio (rad): {theta_eq:.4f}")
print(f"Angolo di equilibrio (deg): {np.degrees(theta_eq):.2f}")

# Verifica: lavoro virtuale a theta_eq
delta_theta_test = 1e-6
lv = virtual_work(theta_eq, delta_theta_test)
print(f"Lavoro virtuale al punto di equilibrio: {lv:.6e}")
Angolo di equilibrio (rad): -0.0983
Angolo di equilibrio (deg): -5.63
Lavoro virtuale al punto di equilibrio: -8.470329e-22

Esercizio del libro privo di senso¶

In [1]:
import numpy as np 

def compute_virtual_displacement(partial_derivatives, delta_q):
    """
    Calculate the virtual displacement.
        :param partial_derivatives: Derivatives of the position vector
         with respect to generalized coordinates.
        :param delta_q: Changes in generalized coordinates.
    :return: Virtual displacement.
    """
    return np.dot(partial_derivatives, delta_q)
    
def principle_of_virtual_work(forces, virtual_displacements):
    """
    Compute the virtual work for given forces and virtual displacements.
        :param forces: List or array of forces acting on the system.
        :param virtual_displacements: List or array of virtual displacements.
    :return: Total virtual work.
    """
    return np.dot(forces, virtual_displacements)
    
def apply_lagrange_multipliers(forces, constraint_directions, lambdas):
    """
    Adjust forces with constraint directions via Lagrange multipliers.
        :param forces: Forces acting on the system.
        :param constraint_directions: Directions of constraint forces.
        :param lambdas: Lagrange multipliers.
    :return: Adjusted forces.
    """
    return forces - np.dot(lambdas, constraint_directions)

def determine_equilibrium(config, generalized_forces, constraints):
    """
    Algorithmic implementation to find equilibrium using virtual work.
        :param config: Initial configuration of the system.
        :param generalized_forces: Generalized forces of the system.
        :param constraints: Constraint equations of the system.
    :return: Equilibrium configuration.
    """
    while True:
        delta_q = np.random.rand(len(config)) # Example small changes
        virtual_displacements = compute_virtual_displacement(np.eye(len(config)), delta_q)
        virtual_work = principle_of_virtual_work(generalized_forces, virtual_displacements)

        if np.isclose(virtual_work, 0, atol=1e-5):
            break
        # Simple update example: adjust to achieve zero virtual work
        config -= 0.01 * virtual_work
    return config

# Example setup for testing the implementation
initial_config = np.array([1.0, 1.0, 1.0])
generalized_forces = np.array([5.0, -3.0, 2.0])
constraints = np.array([0.0]) # Placeholder for no constraints
equilibrium_config = determine_equilibrium(initial_config, generalized_forces, constraints)
print("Equilibrium Configuration:", equilibrium_config)
Equilibrium Configuration: [-4822.81810632 -4822.81810632 -4822.81810632]

Ruota che rotola senza attrito - vincolo NON olonomo¶

In [13]:
import numpy as np

# ------------------------------------------------------------
# PARAMETRI FISICI DELLA RUOTA
# ------------------------------------------------------------
m = 1.0        # massa
R = 0.5        # raggio
I = 0.5 * m * R**2   # momento d'inerzia per disco pieno

# ------------------------------------------------------------
# VINCOLO NON OLONOMO
#   dot_x - R * dot_theta = 0
# ------------------------------------------------------------
def nonholonomic_constraint(q_dot):
    """
    Restituisce il valore del vincolo C(q_dot) = dot_x - R dot_theta
    """
    dot_x, dot_theta = q_dot
    return dot_x - R * dot_theta


# ------------------------------------------------------------
# MATRICE DELLE DERIVATE DEL VINCOLO
#   C_qdot = [1,  -R]
#   serve per costruire il termine lambda * C'
# ------------------------------------------------------------
def constraint_jacobian():
    return np.array([1.0, -R])   # derivata del vincolo rispetto alle velocità


# ------------------------------------------------------------
# EQUAZIONI DI LAGRANGE CON MOLTIPLICATORE
#   m ddot_x =  lambda
#   I ddot_theta = - R lambda
#
#   scritte in forma matriciale:
#       M * q_ddot = lambda * C'
# ------------------------------------------------------------
def mass_matrix():
    return np.diag([m, I])


# ------------------------------------------------------------
# RISOLUZIONE DELL'EQUILIBRIO ISTANTANEO
# (per accelerazioni che soddisfano il vincolo)
#
#   Risolviamo il sistema:
#   [ M   -C'^T ] [ q_ddot ]   = [ 0 ]
#   [ C'    0   ] [ lambda ]     [ 0 ]
#
# È il sistema classico per vincoli non olonomi con Lagrange multipliers
# ------------------------------------------------------------
def solve_accelerations():
    M = mass_matrix()
    C = constraint_jacobian()

    # Costruzione della matrice aumentata:
    # [ M   -C^T ]
    # [ C     0  ]
    A = np.block([
        [M,           -C.reshape(-1,1)],
        [C.reshape(1,-1), np.zeros((1,1))]
    ])

    # Termine di destra (zero perché nessuna forza esterna)
    b = np.zeros(3)

    # Soluzione del sistema lineare
    solution = np.linalg.solve(A, b)

    q_ddot = solution[:2]
    lambda_value = solution[2]

    return q_ddot, lambda_value


# ------------------------------------------------------------
# INTEGRAZIONE TEMPORALE (EULERO)
# ------------------------------------------------------------
def simulate(dt=0.01, T=2.0):
    q = np.array([0.0, 0.0])        # [x, theta]
    q_dot = np.array([1.0, 1.0/R])  # velocità iniziale CONSISTENTE col vincolo

    xs = []
    thetas = []

    for _ in np.arange(0, T, dt):

        # Calcoliamo accelerazioni e lambda
        q_ddot, lam = solve_accelerations()

        # Aggiorniamo velocità e posizione
        q_dot = q_dot + q_ddot * dt
        q = q + q_dot * dt

        xs.append(q[0])
        thetas.append(q[1])

    return np.array(xs), np.array(thetas)


# ------------------------------------------------------------
# ESECUZIONE SIMULAZIONE
# ------------------------------------------------------------
x_values, theta_values = simulate()

print("x final:", x_values[-1])
print("theta final:", theta_values[-1])
x final: 2.0000000000000013
theta final: 4.000000000000003
In [ ]: