Skip to main content

How To Use Secret Formulas

In DynaMaker it is possible to protect your most sensitive calculations by using the Secret Formulas. These are stored in the server and their access is completely restricted when the DynaMaker application is deployed. Although the input and output can be eventually seen somehow, the Secret Formulas act as a black box and it's not possible to see its content and therefore how the input is converted into the output.

For this, we will follow 3 steps:

  1. take the example of the three-point bending test from the tutorial Configurators.
  2. how to create secret formulas.
  3. how to use secret formulas.

1. Example (3-point test)

Two of the most interesting outputs given by a three-point test are the bending moment and how much the beam bends vertically in the middle, also known as the max deflection. Since both outputs concern the beam profile and the load, we could create functions that return them. In here we explain how these can be calculated from a structural-engineering point of view.

A. Bending Moment

Let's start with the bending moment. We would need to calculate the bending moment at every distance or section of the beam depending on the forces (F as the load, Rᵣ & Rₗ as the reactions on the right and left supports) together with the distances (x starting from the left edge of the beam). But don't worry, here are the equations:

As you see the bending moment depends only on the beam length, the forces and where these are located. Then we can write a function like:

function getBendingMomentAtX(load: number, x: number, beamLength: number) {
const leftSupportPositionX = 0.25 * beamLength
const loadPositionX = 0.5 * beamLength
const rightSupportPositionX = 0.75 * beamLength

const leftReaction = load / 2
const rightReaction = load / 2

let bendingMoment = 0
if (x <= leftSupportPositionX) {
bendingMoment = 0
} else if (x > leftSupportPositionX && x <= loadPositionX) {
bendingMoment = leftReaction * (x - leftSupportPositionX)
} else if (x > loadPositionX && x <= rightSupportPositionX) {
bendingMoment = leftReaction * (x - leftSupportPositionX) - load * (x - loadPositionX)
} else {
// bendingMoment = leftReaction * (x - leftSupportPositionX) - load * (x - loadPositionX) + rightReaction * (x - rightSupportPositionX)
bendingMoment = 0
}

return bendingMoment
}

I'm sure you did your calculations correctly and didn't copy the code above... Great! now let's go with the deflection.

B. Deflection

The deflection depends on the cross section (I, area moment of inertia), the material (E, Young's modulus) and the load. Again this is the summary of equations needed for the formula based on the profile type and its shape:

function getDeflectionAtX(
load: number,
x: number,
beamLength: number,
profileType: string,
depth: number,
height: number,
webThickness: number,
flangeThickness: number,
pipeThickness: number,
hollow: boolean,
) {
const leftSupportPositionX = 0.25 * beamLength
const loadPositionX = 0.5 * beamLength
const rightSupportPositionX = 0.75 * beamLength

const L = rightSupportPositionX - leftSupportPositionX

const momentInertia = getAreaMomentOfInertia(
profileType,
depth,
height,
webThickness,
flangeThickness,
pipeThickness,
hollow,
)
const youngModulus = 210 * 1e3 // [N/mm2], common steel at room temperature

const a = loadPositionX - leftSupportPositionX
const b = rightSupportPositionX - loadPositionX

let deflection: number
if (x > leftSupportPositionX && x <= loadPositionX) {
deflection = (load * b * x * (L ** 2 - x ** 2 - b ** 2)) / (6 * L * youngModulus * momentInertia)
} else if (x > loadPositionX && x <= rightSupportPositionX) {
const termInBrackets = (L / b) * (x - a) ** 3 + (L ** 2 - b ** 2) * x - x ** 3
deflection = (load * b * termInBrackets) / (6 * L * youngModulus * momentInertia)
}

return deflection
}

function getAreaMomentOfInertia(
profileType: string,
depth: number,
height: number,
webThickness: number,
flangeThickness: number,
pipeThickness: number,
hollow: boolean,
) {
switch (profileType) {
case 'profile-type-i':
return getAreaMomentInertiaForProfileI(depth, height, webThickness, flangeThickness)
case 'profile-type-i':
return getAreaMomentInertiaForProfileO(depth, height, hollow, pipeThickness)
}
}

function getAreaMomentInertiaForProfileI(
profileWidth: number,
profileHeight: number,
webThickness: number,
flangeThickness: number,
) {
const webHeight = profileHeight - 2 * flangeThickness

const momentInertia =
(webThickness / 12) * webHeight ** 3 + (profileWidth / 12) * (profileHeight ** 3 - webHeight ** 3)

return momentInertia // [mm⁴]
}

function getAreaMomentInertiaForProfileO(
profileWidth: number,
profileHeight: number,
hollow: boolean,
pipeThickness: number,
) {
const A = profileWidth / 2
const B = profileHeight / 2

let momentInertia: number
if (hollow) {
const a = A - pipeThickness
const b = B - pipeThickness
momentInertia = (Math.PI / 4) * (A * B ** 3 - a * b ** 3)
} else {
momentInertia = (Math.PI / 4) * (A * B ** 3)
}
return momentInertia // [mm⁴]
}

2. Create Secret Formulas

Great! Imagine that these formulas are confidential and you don't want any user to have access to them somehow. To "protect" them we can move them to the Secret Formulas section.

To have access to Secret Formulas, you need to have a DynaMaker Pro subscription. If you don't have it, you can still read the following section to get a sense of its potential.

Let's protect these formulas of the bending moment and deflection from the end-user. For that, we can create a couple of secret formulas in the app dashboard: one as BendingMoment and the other as Deflection.

If you go into one of them, you will be in the Secret Formulas editor. This editor is much simpler than the component, UI or drawing editor since it only has three tabs:

  • TESTS: where you can test your formula and make sure it's giving the correct output for different values.
  • FORMULA: where the function with the actual formula as a function will be placed.
  • API: where you can have more control on what to export and use in other places of your app.

So the idea now is to place the functions in here and add tests for the edge cases to make sure we get the correct output, so:

  • in BendingMoment within FORMULA add the function from section A. Bending Moment like:
export async function getBendingMomentAtX(input: { load: number; x: number; beamLength: number }) {
const { load, x, beamLength } = input
// corresponding code
}
  • in Deflection within FORMULA add from section B. Deflection like:
export async function getDeflectionAtX(input: {
load: number
x: number
beamLength: number
profileType: string
depth: number
height: number
webThickness: number
flangeThickness: number
pipeThickness: number
hollow: boolean
}) {
const { load, x, beamLength, profileType, depth, height, webThickness, flangeThickness, pipeThickness, hollow } =
input
// corresponding code
}

function getAreaMomentOfInertia(properties) {
// corresponding code
}

function getAreaMomentInertiaForProfileO(
profileWidth: number,
profileHeight: number,
hollow: boolean,
pipeThickness: number,
) {
// corresponding code
}

Notice that the function to be exported must only have one input, then we need to wrap all the needed arguments in an object called e.g. input. Also, since these formulas are run in the server, remember to type async before the definition of the function and export it to be able to use them later in your application.

Since the secret formulas are meant to be decoupled from the components, you can't import components into the formulas and therefore use some component constants that you might be exporting. Either use hard-coded values to start with or preferably pass them as inputs together with the rest of the properties needed.

Good! Now that we have a formula we want to test it to make sure we are getting the right output given a certain input. We use the TESTS tab for that and it's very easy to use and most important debuggable!

  • in BendingMoment within TESTS, you could add the following 5 edge-case examples:
describe('getBendingMomentAtX', function () {
const load = 50
const beamLength = 300
it('should return 0 at x = 75 (left support)', async () => {
const result = await FORMULA.getBendingMomentAtX({ load, x: 75, beamLength })
expect(result).equal(0)
expect(result).to.not.be.greaterThan(0)
})
it('should return 25 at x = 76 (a bit to the right of the left support', async () => {
const result = await FORMULA.getBendingMomentAtX({ load, x: 76, beamLength })
expect(result).equal(25)
expect(result).to.be.greaterThan(0)
})
it('should return 1875 at x = 150 (center of beam)', async () => {
const result = await FORMULA.getBendingMomentAtX({ load, x: 150, beamLength })
expect(result).equal(1875) // max bending moment
})
it('should return 25 at x = 224 (a bit to the left of the right support)', async () => {
const result = await FORMULA.getBendingMomentAtX({ load, x: 224, beamLength })
expect(result).equal(25)
})
it('should return 0 at x = 225 (right support)', async () => {
const result = await FORMULA.getBendingMomentAtX({ load, x: 225, beamLength })
expect(result).equal(0)
})
})
  • Save & Update to see the status of the tests on the left.
  • Publish to be able to use the formula later in your application.

Repeat a similar process for the Deflection formula and the tests you consider that cover the edge cases. You can use the following test to calibrate your deflection formula correctly:

describe('getDeflectionAtX', function () {
const load = 50
const beamLength = 300
const depth = 100
const height = 100
const profileType = 'profile-type-i'
const webThickness = 50
const flangeThickness = 10
const hollow = undefined
const pipeThickness = undefined

it('should return -0.000027 at x = 150 (center beam)', async () => {
const result = await FORMULA.getDeflectionAtX({
load,
x: 150,
beamLength,
depth,
height,
profileType,
webThickness,
flangeThickness,
hollow,
pipeThickness,
})
expect(result).to.be.approximately(-0.0000027, 0.0000001)
expect(result).to.be.lessThan(0)
})
})

These tests have a chainable language, you can read more about the syntax needed with examples here using most of the common chainable getters like equal, greaterThan and much more.

3. Use Secret Formulas

Since the Secret Formulas are run in the server side and are meant to be decoupled from the components, these can only be used directly in the UI Editor. If they are to be used in the component or in a drawing, then the output of said formulas must be sent somehow to the component in the form of a property or some sort of input to one of its component functions, but always through the UI Editor.

As example of use for the Secret Formulas, we will show their output in some text at the top-right corner of the app UI, also known as metrics. So:

  • import both formulas BendingMoment & Deflection into the UI Editor as if they were subcomponents.

  • create a function in ACTIONS that returns the result of both formulas, given the right input:

    export async function getMetrics() {
    const component = getMainComponent()
    const { width, profileType, depth, height, webThickness, flangeThickness, pipeThickness, hollow } =
    component.getProperties()
    const LOAD = 50 // [N]
    const maxValuePosX = width / 2

    return {
    maxBendingMoment: await BENDINGMOMENT.getBendingMomentAtX({
    load: LOAD,
    x: maxValuePosX,
    beamLength: width,
    }),
    maxDeflection: await DEFLECTION.getDeflectionAtX({
    load: LOAD,
    x: maxValuePosX,
    width,
    profileType,
    depth,
    height,
    webThickness,
    flangeThickness,
    pipeThickness,
    hollow,
    }),
    }
    }

Again, since these functions are run in the server and therefore "protected", getMetrics() needs to be async and has to await for the async functions. Also, you should see the functions when typing BENDINGMOMENT or DEFLECTION if you export them correctly within the secret formula.

In this case, we want to update the metrics for every change of the configurator. Therefore a good place to call these async metrics could be the callback of said configurator. Therefore it has to be async too, like the following:

configurator.addCompletionCallback(async (valid, values) => {
if (valid) {
const metrics = await ACTIONS.getMetrics()
Studio.defineVisibleMetrics([
{
id: 'maxBendingMoment',
value: `${Math.round(metrics.maxBendingMoment)} N/mm²`,
label: `Max bending moment: `,
},
{
id: 'maxDeflection',
value: `${(Math.round(metrics.maxDeflection * 10 ** 8) / 10 ** 2).toFixed(2)} ·10⁶ mm`,
label: `Max deflection: `,
},
])
}
})

If we put this into the final application, it could look something like the following: