Genetic-OptimizationButton

Here’s a fun thing I’ve built as a Proof of Concept: the Genetic OptimizationButton.

Recently I used our AppBuilder to setup a nice truss calculator. Very easily this resulted into supports / loads and beams, and performed checks.

Now here’s the catch: I’d like to minimize the amount of material used. So I’ll have to update the cross sections, and then check if all checks are still passing. And then update again. And again. And again.

This is generally where optimizations come in, and I’d use the OptimizationButton. However with this truss, I have 7 cross sections with a configurable area. I need a smarter optimization button.

Optimization button

Therefore I thought I’d lay the foundations for a more complex Optimization Button; the Genetic OptimizationButton.

By simply wrapping the optimization function with the genetic_optimize decorator, it turns the function into the function that will be ran many, many times in the algorithm. The genetic_optimize will automatically translate the inputs into genes, and will find the optimal values!

So by just these lines:

import viktor as vkt

from optimize_wrapper import genetic_optimize


class Parametrization(vkt.Parametrization):
    x = vkt.NumberField("X")
    y = vkt.NumberField("Y")

class Controller(vkt.Controller):
    parametrization = Parametrization

    @genetic_optimize(
        optimized_parameters=Parametrization,
    )
    def optimize_x_and_y(self, optimized_params, params, **kwargs):
        return optimized_params.x + optimized_params.y

I’d have a working application that will optimize x + y.

What does the wrapper do?

  • It gets your Parametrization
  • It gets the Fields from Parametrization, or from a specific Section/Tab/Page
  • It creates a Gene field from the Fields
  • It then runs the Controller function for everyone in the population. (The Controller function has optimized_params which change every run, and access to the normal kwargs)
  • After finished, it will display the Results like the OptimizationButton with a nice image!

Result

So for my areas optimization this was my final function

Example areas optimization
@genetic_optimize(
    optimized_parameters=Parametrization,
    path="areas_section",
    sol_per_pop=1000,
    num_generations=500,
    amount_of_solutions=50
)
def optimize_areas(self, optimized_params, params, **kwargs):
    # Extract input data and perform analysis
    nodes = list(params.geometry_section.nodes_table)
    members = list(params.geometry_section.members_table)
    loads = list(params.loads_section.loads_table)
    supports = list(params.supports_section.supports_table)

    # THESE ARE OPTIMIZED
    areas_data = list(optimized_params.areas_section.areas_table) 
    # Proceed normally       
    
    E = params.material_section.youngs_modulus
    stress_limit = params.analysis_section.stress_limit
    
    num_nodes = len(nodes)
    
    # Perform analysis
    K_global = self._assemble_stiffness_matrix(nodes, members, areas_data, E)
    F_global = np.zeros(2 * num_nodes)
    for load in loads:
        node_idx = load['node'] - 1
        F_global[2*node_idx] = load['fx']
        F_global[2*node_idx+1] = load['fy']
    
    K_modified, F_modified, _ = self._apply_boundary_conditions(K_global, F_global, supports)
    displacements = np.linalg.solve(K_modified, F_modified)
    member_results = self._calculate_member_forces(nodes, members, areas_data, displacements, E)
    
    # The fitness function is the total area of all crossections, with an insane penalty for if stress is over limit
    for result in member_results:
        stress = result['stress']
        if abs(stress) > stress_limit:
            return - sum([a.area for a in areas_data]) * 100
    return - sum([a.area for

I think this is a very handy setup, which can be improved upon massively. But I thought I share it for inspiration!

2 Likes

@Wichard this could be of interest to you.