Design Methods

What is a design method?!

Design methods allow component designers to identify, from expert knowledge, the different ways users can design a component from functional requirements.

Declaring a design method in a system

Design methods are declared at System setup, using System.add_design_method. This class method takes the name of the design method as single argument; it will create a new entry in an internal dictionary of MathematicalProblem objects, mapped to their names.

Such objects bear unknowns and equations, declared with methods add_unkown and add_equation:

class MySystem(System):

    def setup(self):
        self.add_inward('x', 1.0)
        self.add_outward('y', 0.0)

        design = self.add_design_method('design_x')     # create problem `design`, and store it with key 'design_x'
        design.add_unknown('x').add_equation('y == 0')  # define problem by declaring unknowns and equations

    def compute(self):
        self.y = self.x**2 - 3

In practice, design methods are mathematical problems that can be activated on demand.

Example

[1]:
from cosapp.base import System, Port

class XPort(Port):
    def setup(self):
        self.add_variable("x", 1.0)

class MultiplyWithDesignMethod(System):

    def setup(self):
        self.add_input(XPort, 'p_in', {'x': 1.})
        self.add_output(XPort, 'p_out', {'x': 1.})

        self.add_inward('K1', 5.)

        # off-design constraints
        self.add_inward('expected_output', 1.0)
        self.add_unknown('p_in.x').add_equation('p_out.x == expected_output')

        # design methods
        self.add_inward('dx_design', 10.)
        self.add_design_method('dx').add_unknown('K1').add_equation('p_out.x - p_in.x == dx_design')

    def compute(self):
        self.p_out.x = self.p_in.x * self.K1

Class MultiplyWithDesignMethod defines two types of mathematical problems, through add_unknown and add_equation:

  1. Unknowns and equations declared directly on the system (that is self.add_unknown and self.add_equation in system setup) are always enforced, for all instances of the class. They are referred to as the off-design problem of the class. Composite systems automatically collect the off-design problems of their sub-systems.

  2. Unknowns and equations declared within a design method define a design problem, which may or may not be activated.

Solving the off-design problem

[2]:
from cosapp.drivers import NonLinearSolver, RunSingleCase

m = MultiplyWithDesignMethod('m')
# Add solver
solver = m.add_driver(NonLinearSolver('solver', tol=1e-12))

m.K1 = 5
m.expected_output = 7.5
m.run_drivers()

print("Off-design problem:", solver.problem, sep="\n")

print(
    "Off-design result:",
    f"m.K1 = {m.K1}",
    f"m.p_in.x = {m.p_in.x}",
    f"m.p_out.x = {m.p_out.x}",
    sep="\n  ",
)
Off-design problem:
Unknowns
  runner[p_in.x]
Equations
  p_out.x == expected_output := 0.0

Off-design result:
  m.K1 = 5
  m.p_in.x = 1.5
  m.p_out.x = 7.5

Activating a design method

Design methods are activated by extending an existing mathematical problem with the predefined design method. In the example below, a single-point design case is created using design method 'dx' of the system of interest.

[3]:
from cosapp.drivers import NonLinearSolver, RunSingleCase

m = MultiplyWithDesignMethod('m')

solver = m.add_driver(NonLinearSolver('solver', tol=1e-12))

# Add design point
case = solver.add_child(RunSingleCase('case'))

# Define case conditions
case.set_values({
    'expected_output': 7.5,
    'dx_design': 5.0,
})

case.design.extend(m.design_methods['dx'])  # activate design method 'dx' of system `m`

m.run_drivers()

print("Design problem:", solver.problem, sep="\n")

print(
    "Design result:",
    f"m.K1 = {m.K1}",
    f"m.p_in.x = {m.p_in.x}",
    f"m.p_out.x = {m.p_out.x}",
    sep="\n  ",
)
Design problem:
Unknowns
  inwards.K1
  case[p_in.x]
Equations
  p_out.x - p_in.x == dx_design := 0.0
  p_out.x == expected_output := 0.0

Design result:
  m.K1 = 3.0
  m.p_in.x = 2.5
  m.p_out.x = 7.5

Promoting sub-system design methods at parent level

Composite systems can take advantage of design methods defined for their sub-systems, and thus construct composite design methods.

class CompositeSystem(System):

    def setup(self):
        a = self.add_child(ComponentA('a'))
        b = self.add_child(ComponentB('b'))

        design = self.add_design_method('design')
        design.extend(a.design_methods['design_this'])
        design.extend(b.design_methods['design_that'])

Congrats! You are now ready to update your System into a design model with CoSApp!

[ ]: