diff --git a/trussme/evaluate.py b/trussme/evaluate.py index 88823ae..2154a41 100644 --- a/trussme/evaluate.py +++ b/trussme/evaluate.py @@ -1,5 +1,7 @@ from typing import TypedDict + import numpy +from numpy.typing import NDArray TrussInfo = TypedDict("TrussInfo", { @@ -12,13 +14,11 @@ }) -def the_forces(truss_info: TrussInfo) -> tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, float]: +def the_forces(truss_info: TrussInfo) -> tuple[NDArray[float], NDArray[float], NDArray[float], float]: tj: numpy.ndarray = numpy.zeros([3, numpy.size(truss_info["connections"], axis=1)]) - w: numpy.ndarray = numpy.array([ - numpy.size(truss_info["reactions"], axis=0), - numpy.size(truss_info["reactions"], axis=1) - ]) - dof: numpy.ndarray = numpy.zeros([3*w[1], 3*w[1]]) + w: numpy.ndarray = numpy.array( + [numpy.size(truss_info["reactions"], axis=0), numpy.size(truss_info["reactions"], axis=1)]) + dof: numpy.ndarray = numpy.zeros([3 * w[1], 3 * w[1]]) deflections: numpy.ndarray = numpy.ones(w) deflections -= truss_info["reactions"] @@ -28,19 +28,15 @@ def the_forces(truss_info: TrussInfo) -> tuple[numpy.ndarray, numpy.ndarray, num # Build the global stiffness matrix for i in range(numpy.size(truss_info["connections"], axis=1)): ends = truss_info["connections"][:, i] - length_vector = truss_info["coordinates"][:, ends[1]] \ - - truss_info["coordinates"][:, ends[0]] + length_vector = truss_info["coordinates"][:, ends[1]] - truss_info["coordinates"][:, ends[0]] length = numpy.linalg.norm(length_vector) - direction = length_vector/length + direction = length_vector / length d2 = numpy.outer(direction, direction) - ea_over_l = truss_info["elastic_modulus"][i]*truss_info["area"][i]\ - / length - ss = ea_over_l*numpy.concatenate((numpy.concatenate((d2, -d2), axis=1), - numpy.concatenate((-d2, d2), axis=1)), - axis=0) - tj[:, i] = ea_over_l*direction - e = list(range((3*ends[0]), (3*ends[0] + 3))) \ - + list(range((3*ends[1]), (3*ends[1] + 3))) + ea_over_l = truss_info["elastic_modulus"][i] * truss_info["area"][i] / length + ss = ea_over_l * numpy.concatenate((numpy.concatenate((d2, -d2), axis=1), numpy.concatenate((-d2, d2), axis=1)), + axis=0) + tj[:, i] = ea_over_l * direction + e = list(range((3 * ends[0]), (3 * ends[0] + 3))) + list(range((3 * ends[1]), (3 * ends[1] + 3))) for ii in range(6): for j in range(6): dof[e[ii], e[j]] += ss[ii, j] @@ -56,15 +52,13 @@ def the_forces(truss_info: TrussInfo) -> tuple[numpy.ndarray, numpy.ndarray, num ff = numpy.where(deflections.T == 1) for i in range(len(ff[0])): deflections[ff[1][i], ff[0][i]] = flat_deflections[i] - forces = numpy.sum(numpy.multiply( - tj, deflections[:, truss_info["connections"][1, :]] - - deflections[:, truss_info["connections"][0, :]]), axis=0) + forces = numpy.sum(numpy.multiply(tj, + deflections[:, truss_info["connections"][1, :]] - deflections[:, truss_info["connections"][0, :]]), axis=0) # Check the condition number, and warn the user if it is out of range cond = numpy.linalg.cond(SSff) # Compute the reactions - reactions = numpy.sum(dof*deflections.T.flat[:], axis=1)\ - .reshape([w[1], w[0]]).T + reactions = numpy.sum(dof * deflections.T.flat[:], axis=1).reshape([w[1], w[0]]).T return forces, deflections, reactions, cond diff --git a/trussme/joint.py b/trussme/joint.py index e72ccf0..b512df4 100644 --- a/trussme/joint.py +++ b/trussme/joint.py @@ -1,9 +1,10 @@ import numpy - +from numpy.typing import NDArray +from typing import Literal class Joint(object): - def __init__(self, coordinates: numpy.ndarray): + def __init__(self, coordinates: NDArray[float]): # Save the joint id self.idx = -1 @@ -25,7 +26,7 @@ def __init__(self, coordinates: numpy.ndarray): # Loads self.deflections = numpy.zeros([3, 1]) - def free(self, d:int = 3): + def free(self, d: int = 3): self.translation = numpy.zeros([3, 1]) # If 2d, add out of plane support if d is 2: @@ -35,8 +36,8 @@ def pinned(self, d: int = 3): # Restrict all translation self.translation = numpy.ones([3, 1]) - def roller(self, axis: str = 'y', d: int = 3): - # Only support reaction along denotated axis + def roller(self, axis: Literal["x", "y"] = 'y', d: int = 3): + # Only support reaction along denoted axis self.translation = numpy.zeros([3, 1]) self.translation[ord(axis)-120] = 1 diff --git a/trussme/member.py b/trussme/member.py index b6d0b7c..70ecf3b 100644 --- a/trussme/member.py +++ b/trussme/member.py @@ -2,6 +2,7 @@ import typing import warnings from trussme.physical_properties import Material, MATERIALS +from trussme.joint import Joint class Member(object): @@ -9,37 +10,37 @@ class Member(object): # Shape types shapes: list[str] = ["pipe", "bar", "square", "box"] - def __init__(self, joint_a: int, joint_b: int): + def __init__(self, joint_a: Joint, joint_b: Joint): # Save id number self.idx = -1 # Shape independent variables - self.shape = '' - self.t = 0.0 # thickness - self.w = 0.0 # outer width - self.h = 0.0 # outer height - self.r = 0.0 # outer radius + self.shape: str = '' + self.t: float = 0.0 # thickness + self.w: float = 0.0 # outer width + self.h: float = 0.0 # outer height + self.r: float = 0.0 # outer radius # Material properties - self.material = '' # string specifying material - self.elastic_modulus = 0.0 # Elastic modulus - self.Fy = 0.0 # yield strength - self.rho = 0.0 # material density + self.material: str = '' # string specifying material + self.elastic_modulus: float = 0.0 # Elastic modulus + self.Fy: float = 0.0 # yield strength + self.rho: float = 0.0 # material density # Dependent variables - self.area = 0.0 # Cross-sectional area - self.I = 0.0 # Moment of inertia - self.LW = 0.0 # Linear weight + self.area: float = 0.0 # Cross-sectional area + self.I: float = 0.0 # Moment of inertia + self.LW: float = 0.0 # Linear weight # Variables to store information about truss state - self.force = 0 - self.fos_yielding = 0 - self.fos_buckling = 0 - self.mass = 0 + self.force: float = 0 + self.fos_yielding: float = 0 + self.fos_buckling: float = 0 + self.mass: float = 0 # Variable to store location in truss self.joints = [joint_a, joint_b] - self.length = 0 + self.length: float = 0.0 self.end_a = [] self.end_b = [] diff --git a/trussme/report.py b/trussme/report.py index 932375e..fb83049 100644 --- a/trussme/report.py +++ b/trussme/report.py @@ -1,18 +1,19 @@ import numpy import pandas import trussme.physical_properties as pp +from trussme.truss import Truss -def print_summary(f, the_truss, verb=False): - pw(f, "\n", v=verb) - pw(f, "(0) SUMMARY OF ANALYSIS", v=verb) - pw(f, "=============================", v=verb) +def print_summary(f, the_truss: Truss, verbose: bool = False): + pw(f, "\n", v=verbose) + pw(f, "(0) SUMMARY OF ANALYSIS", v=verbose) + pw(f, "=============================", v=verbose) pw(f, "\t- The truss has a mass of " + format(the_truss.mass, '.2f') + " kg, and a total factor of safety of " + format(the_truss.fos_total, '.2f') - + ". ", v=verb) - pw(f, "\t- The limit state is " + the_truss.limit_state + ".", v=verb) + + ". ", v=verbose) + pw(f, "\t- The limit state is " + the_truss.limit_state + ".", v=verbose) if the_truss.THERE_ARE_GOALS: success_string = [] @@ -47,43 +48,43 @@ def print_summary(f, the_truss, verb=False): if len(success_string) is not 0: if len(success_string) is 1: pw(f, "\t- The design goal for " + str(success_string[0]) - + " was satisfied.", v=verb) + + " was satisfied.", v=verbose) elif len(success_string) is 2: pw(f, "\t- The design goals for " + str(success_string[0]) + " and " + str(success_string[1]) - + " were satisfied.", v=verb) + + " were satisfied.", v=verbose) else: - pw(f, "\t- The design goals for ", nl=False, v=verb) + pw(f, "\t- The design goals for ", nl=False, v=verbose) for st in success_string[0:-1]: - pw(f, st+", ", nl=False, v=verb) - pw(f, "and "+str(success_string[-1])+" were satisfied.", v=verb) + pw(f, st+", ", nl=False, v=verbose) + pw(f, "and "+str(success_string[-1])+" were satisfied.", v=verbose) if len(failure_string) is not 0: if len(failure_string) is 1: pw(f, "\t- The design goal for " + str(failure_string[0]) - + " was not satisfied.", v=verb) + + " was not satisfied.", v=verbose) elif len(failure_string) is 2: pw(f, "\t- The design goals for " + str(failure_string[0]) + " and " + str(failure_string[1]) - + " were not satisfied.", v=verb) + + " were not satisfied.", v=verbose) else: - pw(f, "\t- The design goals for", nl=False, v=verb) + pw(f, "\t- The design goals for", nl=False, v=verbose) for st in failure_string[0:-1]: - pw(f, st+",", nl=False, v=verb) - pw(f, "and "+str(failure_string[-1])+" were not satisfied.", v=verb) + pw(f, st+",", nl=False, v=verbose) + pw(f, "and "+str(failure_string[-1])+" were not satisfied.", v=verbose) -def print_instantiation_information(f, the_truss, verb=False): - pw(f, "\n", v=verb) - pw(f, "(1) INSTANTIATION INFORMATION", v=verb) - pw(f, "=============================", v=verb) +def print_instantiation_information(f, the_truss, verbose=False): + pw(f, "\n", v=verbose) + pw(f, "(1) INSTANTIATION INFORMATION", v=verbose) + pw(f, "=============================", v=verbose) # Print joint information - pw(f, "\n--- JOINTS ---", v=verb) + pw(f, "\n--- JOINTS ---", v=verbose) data = [] rows = [] for j in the_truss.joints: @@ -103,10 +104,10 @@ def print_instantiation_information(f, the_truss, verb=False): "X-Support", "Y-Support", "Z-Support"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) # Print member information - pw(f, "\n--- MEMBERS ---", v=verb) + pw(f, "\n--- MEMBERS ---", v=verbose) data = [] rows = [] for m in the_truss.members: @@ -130,11 +131,11 @@ def print_instantiation_information(f, the_truss, verb=False): "Width(m)", "Radius(m)", "Thickness(m)"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) # Print material list unique_materials = list({v['name']:v for v in the_truss.materials}.values()) - pw(f, "\n--- MATERIALS ---", v=verb) + pw(f, "\n--- MATERIALS ---", v=verbose) data = [] rows = [] for mat in unique_materials: @@ -149,16 +150,16 @@ def print_instantiation_information(f, the_truss, verb=False): columns=["Density(kg/m3)", "Elastic Modulus(GPa)", "Yield Strength(MPa)"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) -def print_stress_analysis(f, the_truss, verb=False): - pw(f, "\n", v=verb) - pw(f, "(2) STRESS ANALYSIS INFORMATION", v=verb) - pw(f, "===============================", v=verb) +def print_stress_analysis(f, the_truss, verbose=False): + pw(f, "\n", v=verbose) + pw(f, "(2) STRESS ANALYSIS INFORMATION", v=verbose) + pw(f, "===============================", v=verbose) # Print information about loads - pw(f, "\n--- LOADING ---", v=verb) + pw(f, "\n--- LOADING ---", v=verbose) data = [] rows = [] for j in the_truss.joints: @@ -174,10 +175,10 @@ def print_stress_analysis(f, the_truss, verb=False): columns=["X-Load", "Y-Load", "Z-Load"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) # Print information about reactions - pw(f, "\n--- REACTIONS ---", v=verb) + pw(f, "\n--- REACTIONS ---", v=verbose) data = [] rows = [] for j in the_truss.joints: @@ -194,10 +195,10 @@ def print_stress_analysis(f, the_truss, verb=False): columns=["X-Reaction(kN)", "Y-Reaction(kN)", "Z-Reaction(kN)"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) # Print information about members - pw(f, "\n--- FORCES AND STRESSES ---", v=verb) + pw(f, "\n--- FORCES AND STRESSES ---", v=verbose) data = [] rows = [] for m in the_truss.members: @@ -215,10 +216,10 @@ def print_stress_analysis(f, the_truss, verb=False): "Axial-force(kN)", "FOS-yielding", "FOS-buckling"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) # Print information about members - pw(f, "\n--- DEFLECTIONS ---", v=verb) + pw(f, "\n--- DEFLECTIONS ---", v=verbose) data = [] rows = [] for j in the_truss.joints: @@ -235,14 +236,14 @@ def print_stress_analysis(f, the_truss, verb=False): columns=["X-Defl.(mm)", "Y-Defl.(mm)", "Z-Defl.(mm)"]) - .to_string(justify="left"), v=verb) + .to_string(justify="left"), v=verbose) -def print_recommendations(f, the_truss, verb=False): +def print_recommendations(f, the_truss, verbose=False): made_a_recommendation = False - pw(f, "\n", v=verb) - pw(f, "(3) RECOMMENDATIONS", v=verb) - pw(f, "===============================", v=verb) + pw(f, "\n", v=verbose) + pw(f, "(3) RECOMMENDATIONS", v=verbose) + pw(f, "===============================", v=verbose) if the_truss.goals["max_mass"] is not -1: tm = the_truss.goals["max_mass"] @@ -262,28 +263,28 @@ def print_recommendations(f, the_truss, verb=False): if m.fos_yielding < tyf: pw(f, "\t- Member_"+'{0:02d}'.format(m.idx)+" is yielding. " - "Try increasing the cross-sectional area.", v=verb) - pw(f, "\t\t- Current area: " + format(m.I, '.2e') + " m^2", v=verb) + "Try increasing the cross-sectional area.", v=verbose) + pw(f, "\t\t- Current area: " + format(m.I, '.2e') + " m^2", v=verbose) pw(f, "\t\t- Recommended area: " + format(m.area*the_truss.goals["min_fos_yielding"] - / m.fos_yielding, '.2e') + " m^2", v=verb) + / m.fos_yielding, '.2e') + " m^2", v=verbose) pw(f, "\t\t- Try increasing member dimensions by a factor of " "at least " + format(pow(the_truss.goals["min_fos_yielding"] - / m.fos_yielding, 0.5), '.3f'), v=verb) + / m.fos_yielding, 0.5), '.3f'), v=verbose) made_a_recommendation = True if 0 < m.fos_buckling < tbf: pw(f, "\t- Member_"+'{0:02d}'.format(m.idx)+" is buckling. " - "Try increasing the moment of inertia.", v=verb) + "Try increasing the moment of inertia.", v=verbose) pw(f, "\t\t- Current moment of inertia: " - + format(m.I, '.2e') + " m^4", v=verb) + + format(m.I, '.2e') + " m^4", v=verbose) pw(f, "\t\t- Recommended moment of inertia: " + format(m.I*the_truss.goals["min_fos_buckling"] - / m.fos_buckling, '.2e') + " m^4", v=verb) + / m.fos_buckling, '.2e') + " m^4", v=verbose) pw(f, "\t\t- Try increasing member dimensions by a factor of " "at least " + format(pow(the_truss.goals["min_fos_buckling"] / m.fos_buckling, 0.25), '.3f') - + ".", v=verb) + + ".", v=verbose) made_a_recommendation = True if m.fos_buckling > tbf \ @@ -292,7 +293,7 @@ def print_recommendations(f, the_truss, verb=False): if the_truss.mass > the_truss.goals["max_mass"]: pw(f, "\t- Member_"+'{0:02d}'.format(m.idx)+" is strong " "enough, so try decreasing the cross-sectional area " - "to decrease mass.", v=verb) + "to decrease mass.", v=verbose) made_a_recommendation = True for j in the_truss.joints: @@ -304,12 +305,12 @@ def print_recommendations(f, the_truss, verb=False): if numpy.linalg.norm(j.deflections) > td: pw(f, "\t- Joint_"+'{0:02d}'.format(j.idx)+" is deflecting " "excessively. Try increasing the cross-sectional area of " - "adjacent members. These include:", v=verb) + "adjacent members. These include:", v=verbose) for m in j.members: - pw(f, "\t\t- Member_"+'{0:02d}'.format(m.idx), v=verb) + pw(f, "\t\t- Member_"+'{0:02d}'.format(m.idx), v=verbose) if not made_a_recommendation: - pw(f, "No recommendations. All design goals met.", v=verb) + pw(f, "No recommendations. All design goals met.", v=verbose) def pw(f, string, nl=True, v=False): diff --git a/trussme/truss.py b/trussme/truss.py index 9f34b5c..701d22f 100644 --- a/trussme/truss.py +++ b/trussme/truss.py @@ -1,6 +1,7 @@ import numpy -from trussme import joint -from trussme import member +from numpy.typing import NDArray +from trussme.joint import Joint +from trussme.member import Member from trussme import report from trussme import evaluate from trussme.physical_properties import g, Material, MATERIALS @@ -13,23 +14,23 @@ class Truss(object): def __init__(self, file_name=""): # Make a list to store members in - self.members = [] + self.members: list[Member] = [] # Make a list to store joints in - self.joints = [] + self.joints: list[Joint] = [] # Make a list to store materials in self.materials: list[Material] = [MATERIALS[0]] # Variables to store number of joints and members - self.number_of_joints = 0 - self.number_of_members = 0 + self.number_of_joints: int = 0 + self.number_of_members: int = 0 # Variables to store truss characteristics - self.mass = 0 - self.fos_yielding = 0 - self.fos_buckling = 0 - self.fos_total = 0 + self.mass: float = 0.0 + self.fos_yielding: float = 0.0 + self.fos_buckling: float = 0.0 + self.fos_total: float = 0.0 self.limit_state = '' self.condition = 0 @@ -104,23 +105,23 @@ def set_goal(self, **kwargs): 'min_fos_buckling, ' 'max_mass, or max_deflection.') - def add_support(self, coordinates, d=3): + def add_support(self, coordinates: NDArray[float], d: int = 3): # Make the joint - self.joints.append(joint.Joint(coordinates)) + self.joints.append(Joint(coordinates)) self.joints[self.number_of_joints].pinned(d=d) self.joints[-1].idx = self.number_of_joints self.number_of_joints += 1 - def add_joint(self, coordinates, d=3): + def add_joint(self, coordinates: NDArray[float], d: int = 3): # Make the joint - self.joints.append(joint.Joint(coordinates)) + self.joints.append(Joint(coordinates)) self.joints[self.number_of_joints].free(d=d) self.joints[-1].idx = self.number_of_joints self.number_of_joints += 1 - def add_member(self, joint_index_a, joint_index_b): + def add_member(self, joint_index_a: int, joint_index_b: int): # Make a member - self.members.append(member.Member(self.joints[joint_index_a], + self.members.append(Member(self.joints[joint_index_a], self.joints[joint_index_b])) self.members[-1].idx = self.number_of_members @@ -131,7 +132,7 @@ def add_member(self, joint_index_a, joint_index_b): self.number_of_members += 1 - def move_joint(self, joint_index, coordinates): + def move_joint(self, joint_index: int, coordinates: NDArray[float]): self.joints[joint_index].coordinates = coordinates def calc_mass(self): @@ -139,8 +140,8 @@ def calc_mass(self): for m in self.members: self.mass += m.mass - def set_load(self, joint_index, load): - self.joints[joint_index].load = load + def set_load(self, joint_index: int, load: NDArray[float]): + self.joints[joint_index].loads = load def calc_fos(self): # Pull supports and add to D @@ -214,7 +215,7 @@ def calc_fos(self): warnings.warn("The condition number is " + str(self.condition) + ". Results may be inaccurate.") - def __report(self, file_name="", verb=False): + def __report(self, file_name: str = "", verb: bool = False): # DO the calcs self.calc_mass() @@ -242,16 +243,16 @@ def __report(self, file_name="", verb=False): if file_name is not "": f.close() - def print_and_save_report(self, file_name): + def print_and_save_report(self, file_name: str): self.__report(file_name=file_name, verb=True) def print_report(self): self.__report(file_name="", verb=True) - def save_report(self, file_name): + def save_report(self, file_name: str): self.__report(file_name=file_name, verb=False) - def save_truss(self, file_name=""): + def save_truss(self, file_name: str = ""): if file_name is "": file_name = time.strftime('%X %x %Z')