-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
332 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
.idea/* | ||
tests/report* | ||
tests/asdf* | ||
tests/optim* | ||
tests/__pychache__/* | ||
trussme/__pycache__/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import unittest | ||
import os | ||
|
||
import scipy.optimize | ||
import numpy | ||
|
||
import trussme | ||
|
||
|
||
class TestCustomStuff(unittest.TestCase): | ||
def test_setup(self): | ||
truss_from_commands = trussme.Truss() | ||
truss_from_commands.add_pinned_joint([0.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([1.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([2.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([3.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([4.0, 0.0, 0.0]) | ||
truss_from_commands.add_pinned_joint([5.0, 0.0, 0.0]) | ||
|
||
truss_from_commands.add_free_joint([0.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([1.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([2.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([3.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([4.5, 1.0, 0.0]) | ||
|
||
truss_from_commands.add_out_of_plane_support("z") | ||
|
||
truss_from_commands.joints[8].loads[1] = -20000 | ||
|
||
truss_from_commands.add_member(0, 1) | ||
truss_from_commands.add_member(1, 2) | ||
truss_from_commands.add_member(2, 3) | ||
truss_from_commands.add_member(3, 4) | ||
truss_from_commands.add_member(4, 5) | ||
|
||
truss_from_commands.add_member(6, 7) | ||
truss_from_commands.add_member(7, 8) | ||
truss_from_commands.add_member(8, 9) | ||
truss_from_commands.add_member(9, 10) | ||
|
||
truss_from_commands.add_member(0, 6) | ||
truss_from_commands.add_member(6, 1) | ||
truss_from_commands.add_member(1, 7) | ||
truss_from_commands.add_member(7, 2) | ||
truss_from_commands.add_member(2, 8) | ||
truss_from_commands.add_member(8, 3) | ||
truss_from_commands.add_member(3, 9) | ||
truss_from_commands.add_member(9, 4) | ||
truss_from_commands.add_member(4, 10) | ||
truss_from_commands.add_member(10, 5) | ||
|
||
goals = trussme.Goals() | ||
|
||
x0, obj, con, gen = trussme.make_optimization_functions( | ||
truss_from_commands, goals | ||
) | ||
|
||
self.assertEqual( | ||
trussme.report_to_str(gen(x0), goals), | ||
trussme.report_to_str(truss_from_commands, goals), | ||
) | ||
|
||
def test_joint_optimization(self): | ||
truss_from_commands = trussme.Truss() | ||
truss_from_commands.add_pinned_joint([0.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([1.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([2.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([3.0, 0.0, 0.0]) | ||
truss_from_commands.add_free_joint([4.0, 0.0, 0.0]) | ||
truss_from_commands.add_pinned_joint([5.0, 0.0, 0.0]) | ||
|
||
truss_from_commands.add_free_joint([0.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([1.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([2.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([3.5, 1.0, 0.0]) | ||
truss_from_commands.add_free_joint([4.5, 1.0, 0.0]) | ||
|
||
truss_from_commands.add_out_of_plane_support("z") | ||
|
||
truss_from_commands.joints[8].loads[1] = -20000 | ||
|
||
truss_from_commands.add_member(0, 1) | ||
truss_from_commands.add_member(1, 2) | ||
truss_from_commands.add_member(2, 3) | ||
truss_from_commands.add_member(3, 4) | ||
truss_from_commands.add_member(4, 5) | ||
|
||
truss_from_commands.add_member(6, 7) | ||
truss_from_commands.add_member(7, 8) | ||
truss_from_commands.add_member(8, 9) | ||
truss_from_commands.add_member(9, 10) | ||
|
||
truss_from_commands.add_member(0, 6) | ||
truss_from_commands.add_member(6, 1) | ||
truss_from_commands.add_member(1, 7) | ||
truss_from_commands.add_member(7, 2) | ||
truss_from_commands.add_member(2, 8) | ||
truss_from_commands.add_member(8, 3) | ||
truss_from_commands.add_member(3, 9) | ||
truss_from_commands.add_member(9, 4) | ||
truss_from_commands.add_member(4, 10) | ||
truss_from_commands.add_member(10, 5) | ||
|
||
goals = trussme.Goals() | ||
|
||
x0, obj, con, gen = trussme.make_optimization_functions( | ||
truss_from_commands, goals, joint_coordinates=True, shape_parameters=False | ||
) | ||
|
||
results = scipy.optimize.minimize( | ||
lambda x, *args: obj(x), | ||
x0, | ||
scipy.optimize.NonlinearConstraint( | ||
con, -numpy.inf, 0.0, keep_feasible=True | ||
), | ||
method="trust-constr", | ||
options={"verbose": 2, "maxiter": 50}, | ||
) | ||
|
||
result_truss = gen(results.x) | ||
result_truss.analyze() | ||
trussme.report_to_md( | ||
os.path.join(os.path.dirname(__file__), "optim.md"), result_truss, goals | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
from typing import Callable | ||
import io | ||
|
||
import numpy | ||
|
||
from trussme import Truss, Goals, read_json | ||
|
||
|
||
def make_x0( | ||
truss: Truss, | ||
joint_coordinates: bool = True, | ||
shape_parameters: bool = True, | ||
) -> list[float]: | ||
""" | ||
Returns a vector that encodes the current truss design | ||
Parameters | ||
---------- | ||
truss: Truss | ||
The truss to configure. | ||
joint_coordinates: bool, default=True | ||
Whether to include joint location parameters. | ||
shape_parameters: bool, default=True | ||
Whether to include shape parameters. | ||
Returns | ||
------- | ||
list[float] | ||
A starting vector that encodes the current truss design | ||
""" | ||
|
||
planar_direction: str = truss.is_planar() | ||
x0: list[float] = [] | ||
|
||
configured_truss = read_json(truss.to_json()) | ||
|
||
if joint_coordinates: | ||
for i in range(len(configured_truss.joints)): | ||
if ( | ||
numpy.sum(configured_truss.joints[i].translation_restricted) | ||
== (0 if planar_direction == "none" else 1) | ||
and numpy.sum(configured_truss.joints[i].loads) == 0 | ||
): | ||
if planar_direction != "x": | ||
x0.append(configured_truss.joints[i].coordinates[0]) | ||
if planar_direction != "y": | ||
x0.append(configured_truss.joints[i].coordinates[1]) | ||
if planar_direction != "z": | ||
x0.append(configured_truss.joints[i].coordinates[2]) | ||
|
||
return x0 | ||
|
||
|
||
def make_truss_generator_function( | ||
truss: Truss, | ||
joint_coordinates: bool = True, | ||
shape_parameters: bool = True, | ||
) -> Callable[[list[float]], Truss]: | ||
""" | ||
Returns a function that takes a list of floats and returns a truss. | ||
Parameters | ||
---------- | ||
truss: Truss | ||
The truss to configure. | ||
joint_coordinates: bool, default=True | ||
Whether to include joint location parameters. | ||
shape_parameters: bool, default=True | ||
Whether to include shape parameters. | ||
Returns | ||
------- | ||
Callable[[list[float]] | ||
A tuple containing a vector that describes the input truss and a function that takes a list of floats and returns a truss. | ||
""" | ||
|
||
planar_direction: str = truss.is_planar() | ||
|
||
def truss_generator(x: list[float]) -> Truss: | ||
configured_truss = read_json(truss.to_json()) | ||
idx = 0 | ||
|
||
if joint_coordinates: | ||
for i in range(len(configured_truss.joints)): | ||
if ( | ||
numpy.sum(configured_truss.joints[i].translation_restricted) | ||
== (0 if planar_direction == "none" else 1) | ||
and numpy.sum(configured_truss.joints[i].loads) == 0 | ||
): | ||
if planar_direction != "x": | ||
configured_truss.joints[i].coordinates[0] = x[idx] | ||
idx += 1 | ||
if planar_direction != "y": | ||
configured_truss.joints[i].coordinates[1] = x[idx] | ||
idx += 1 | ||
if planar_direction != "z": | ||
configured_truss.joints[i].coordinates[2] = x[idx] | ||
idx += 1 | ||
|
||
return configured_truss | ||
|
||
return truss_generator | ||
|
||
|
||
def make_optimization_functions( | ||
truss: Truss, | ||
goals: Goals, | ||
joint_coordinates: bool = True, | ||
shape_parameters: bool = True, | ||
) -> tuple[ | ||
list[float], | ||
Callable[[list[float]], float], | ||
Callable[[list[float]], list[float]], | ||
Callable[[list[float]], Truss], | ||
]: | ||
""" | ||
Creates functions for use in optimization, including a starting vector, objective function, a constraint function, and a truss generator function. | ||
Parameters | ||
---------- | ||
truss: Truss | ||
The truss to use as a starting configuration | ||
joint_coordinates: bool, default=True | ||
Whether to include joint location parameters. | ||
shape_parameters: bool, default=True | ||
Whether to include shape parameters. | ||
Returns | ||
------- | ||
tuple[ | ||
list[float], | ||
Callable[[list[float]], float], | ||
Callable[[list[float]], list[float]], | ||
Callable[[list[float]], Truss], | ||
] | ||
A tuple containing the starting vector, objective function, constraint function, and truss generator function. | ||
""" | ||
|
||
x0 = make_x0(truss, joint_coordinates, shape_parameters) | ||
|
||
truss_generator = make_truss_generator_function( | ||
truss, joint_coordinates, shape_parameters | ||
) | ||
|
||
def objective_function(x: list[float]) -> float: | ||
truss = truss_generator(x) | ||
return truss.mass | ||
|
||
def inequality_constraints(x: list[float]) -> list[float]: | ||
truss = truss_generator(x) | ||
truss.analyze() | ||
return [ | ||
goals.minimum_fos_buckling - truss.fos_buckling, | ||
goals.minimum_fos_yielding - truss.fos_yielding, | ||
truss.deflection - goals.maximum_deflection, | ||
] | ||
|
||
return ( | ||
x0, | ||
objective_function, | ||
inequality_constraints, | ||
truss_generator, | ||
) |
Oops, something went wrong.