Example 01 - Create a simple study for a beverage can design#

  1################################################################################
  2# Copyright (c) 2023 - 2025 Altair Engineering Inc.  All Rights Reserved
  3# Contains trade secrets of Altair Engineering, Inc.  Copyright notice
  4# does not imply publication.  Decompilation or disassembly of this
  5# software is strictly prohibited.
  6################################################################################
  7
  8"""
  9Example HyperStudy Python API usage with hstbatch as a Python module.
 10This aims to mimic the Beverage Can quick start example.
 11"""
 12
 13import alt.hst.api.hstapp as hstapp
 14import os
 15import alt.hst.api.session as sess
 16from alt.hst.api import api_objects
 17import typing
 18
 19
 20# -----------------------------------------------------------------------------
 21def addDesignVariable(designVariableList: api_objects.VariableList, 
 22                      label: str, varname: str, lower: float,
 23                      nominal: float, upper: float, comment: str) -> None:
 24    designVariable = designVariableList.add(varname, label)
 25    designVariable.setLowerBound(lower)
 26    designVariable.setNominalValue(nominal)
 27    designVariable.setUpperBound(upper)
 28    designVariable.setComment(comment)
 29
 30# -----------------------------------------------------------------------------
 31def addAllDesignVariables(designVariableList: api_objects.VariableList) -> None:
 32    addDesignVariable(designVariableList, "Diameter", "diameter", 30.0, 60.0, 90.0, "mm")
 33    addDesignVariable(designVariableList, "Height", "height", 60.0, 120.0, 180.0, "mm")
 34    addDesignVariable(designVariableList, "Thick Top", "thick_top", 0.2, 0.25, 0.3, "mm")
 35    addDesignVariable(designVariableList, "Thick Side", "thick_side", 0.1, 0.12, 0.14, "mm")
 36    addDesignVariable(designVariableList, "Cost Top Bot Material", "cost_tb_mat", 2.0, 5.0, 8.0, "USD/mm2")
 37    addDesignVariable(designVariableList, "Cost Side Material", "cost_side_mat", 1.0, 2.0, 3.0, "USD/mm2")
 38    addDesignVariable(designVariableList, "Cost Rim Manufacturing", "cost_rim", 1.5, 3.0, 4.5, "USD/mm")
 39
 40    # Common design variable attributes can be specified here
 41    for designVariable in designVariableList:
 42        modelParameter = designVariable.getModelParameter()
 43        modelParameter.setModelVarname("m_1")
 44        modelParameter.setName("native")
 45        designVariable.setDataType(api_objects.DataType.REAL)
 46        designVariable.setFormatToolType(api_objects.FormatToolTypes.TYPE_CONTINUOUS)
 47        designVariable.setCategory(api_objects.VariableCategory.CONTROLLED)
 48
 49
 50# -----------------------------------------------------------------------------
 51def addResponse(responseList: api_objects.ResponseList, label: str, 
 52                varname: str, equation: str, comment: str) -> None:
 53    response = responseList.add()
 54    response.setLabel(label)
 55    response.setVarname(varname)
 56    response.setEquation(equation)
 57    response.setComment(comment)
 58
 59
 60# -----------------------------------------------------------------------------
 61def addAllResponses(responseList: api_objects.ResponseList) -> None:
 62    addResponse(responseList, "Area Top", "area_top", "pi*(diameter/2)^2", "mm2")
 63    addResponse(responseList, "Area Top Bot", "area_tb", "2*area_top", "mm2")
 64    addResponse(responseList, "Area Side", "area_side", "height*pi*diameter", "mm2")
 65    addResponse(responseList, "Volume", "vol", "area_top*height", "mm3")
 66    addResponse(responseList, "Material Cost", "mat_cost", "cost_tb_mat*area_tb+cost_side_mat*area_side", "USD")
 67    addResponse(responseList, "Manufacturing Cost", "manu_cost", "2*pi*diameter*cost_rim", "USD")
 68    addResponse(responseList, "Total Cost", "total_cost", "mat_cost+manu_cost", "USD")
 69    addResponse(responseList, "Styling", "styling", "diameter/height", "-")
 70
 71    for response in responseList:
 72        response.setType(api_objects.DataType.REAL)
 73
 74
 75# -----------------------------------------------------------------------------
 76def addGoal(goalList: api_objects.GoalList, 
 77            label: str, 
 78            varname: str, 
 79            response: str, goalType: str, 
 80            boundType: typing.Optional[str] = None, 
 81            boundValue: typing.Optional[float] = None) -> None:
 82    goal = goalList.add(varname, label)
 83    goal.setResponse(response)
 84    goal.setType(goalType)
 85    if boundType is not None:
 86        goal.setBoundType(boundType)
 87    if boundValue is not None:
 88        goal.setBoundValue(boundValue)
 89
 90
 91# -----------------------------------------------------------------------------
 92def addAllGoals(goalList: api_objects.GoalList) -> None:
 93    addGoal(goalList, "Goal 1", "goal_1", "vol", "Maximize")
 94    addGoal(goalList, "Goal 2", "goal_2", "total_cost", "Minimize")
 95    addGoal(goalList, "Goal 3", "goal_3", "styling", "Constraint", ">=", 0.5)
 96
 97
 98# -----------------------------------------------------------------------------
 99def addGradients(dvList: api_objects.VariableList, 
100                 responseList: api_objects.ResponseList, 
101                 gradientList: api_objects.GradientList) -> None:
102    # Create gradients for all the respones/variables
103    for variable in dvList:
104        for response in responseList:
105            gradient = gradientList.add()
106            gradient.setLabel(f"∂{response.getLabel()}/∂{variable.getLabel()}")
107            gradient.setVarname(
108                f"d_{response.getVarname()}_d_{variable.getVarname()}")
109            gradient.setDerivativeOf(response.getVarname())
110            gradient.setRespectTo(variable.getVarname())
111            gradient.setDuplicate(False)
112            gradient.setState(False)
113            gradient.setAlwaysZero(False)
114            gradient.setEquation("0")
115            modelParameter = gradient.getModelParameter()
116            modelParameter.setModelVarname("m_1")
117            modelParameter.setName("native")
118
119    # Only some of the gradients have non-zero equations
120    gradientList["d_area_top_d_diameter"].setEquation("pi*diameter/2")
121    gradientList["d_area_tb_d_diameter"].setEquation("2*d_area_top_d_diameter")
122    gradientList["d_area_side_d_diameter"].setEquation("height*pi")
123    gradientList["d_area_side_d_height"].setEquation("pi*diameter")
124    gradientList["d_vol_d_diameter"].setEquation("d_area_top_d_diameter*height")
125    gradientList["d_vol_d_height"].setEquation("area_top")
126    gradientList["d_mat_cost_d_diameter"].setEquation(
127        "cost_tb_mat*d_area_tb_d_diameter+cost_side_mat*d_area_side_d_diameter")
128    gradientList["d_mat_cost_d_height"].setEquation(
129        "cost_side_mat*d_area_side_d_height")
130    gradientList["d_mat_cost_d_cost_tb_mat"].setEquation("area_tb")
131    gradientList["d_mat_cost_d_cost_side_mat"].setEquation("area_side")
132    gradientList["d_manu_cost_d_diameter"].setEquation("2*pi*cost_rim")
133    gradientList["d_manu_cost_d_cost_rim"].setEquation("2*pi*diameter")
134    gradientList["d_total_cost_d_diameter"].setEquation(
135        "d_mat_cost_d_diameter + d_manu_cost_d_diameter")
136    gradientList["d_total_cost_d_height"].setEquation(
137        "d_mat_cost_d_height + d_manu_cost_d_height")
138    gradientList["d_total_cost_d_thick_top"].setEquation(
139        "d_mat_cost_d_thick_top + d_manu_cost_d_thick_top")
140    gradientList["d_total_cost_d_thick_side"].setEquation(
141        "d_mat_cost_d_thick_side + d_manu_cost_d_thick_side")
142    gradientList["d_total_cost_d_cost_tb_mat"].setEquation(
143        "d_mat_cost_d_cost_tb_mat + d_manu_cost_d_cost_tb_mat")
144    gradientList["d_total_cost_d_cost_side_mat"].setEquation(
145        "d_mat_cost_d_cost_side_mat + d_manu_cost_d_cost_side_mat")
146    gradientList["d_total_cost_d_cost_rim"].setEquation(
147        "d_mat_cost_d_cost_rim + d_manu_cost_d_cost_rim")
148    gradientList["d_styling_d_diameter"].setEquation(
149        "1/height")
150    gradientList["d_styling_d_height"].setEquation("diameter*(-1/height^2)")
151
152
153# -----------------------------------------------------------------------------
154def addVariableConstraints(constraintList: api_objects.ConstraintList) -> None:
155    constraint_1 = constraintList.add("con_1", "Constraint 1")
156    constraint_1.setState(False)
157    constraint_1.setLeftExpression("3*diameter")
158    constraint_1.setRightExpression("height")
159    constraint_1.setComparisonType(">=")
160
161    constraint_2 = constraintList.add("con_2", "Constraint 2")
162    constraint_2.setState(False)
163    constraint_2.setLeftExpression("diameter")
164    constraint_2.setRightExpression("height - 20")
165    constraint_2.setComparisonType("<=")
166
167
168###############################################################################
169# Start hstbatch
170###############################################################################
171# If used in a HyperWorks context, avoidHwPreferences needs to be set True.
172# The API cannot be used until HyperStudy is started and initalized.
173app = hstapp.HstBatchApp(avoidHwPreferences=True)
174app.start(["-v", "0"])
175
176
177###############################################################################
178# Create a new study
179###############################################################################
180studyList = sess.getStudyList()
181study = studyList.add("beverage_can", "Beverage Can")
182studyDir = os.path.join(os.getcwd(), 'hstbatch_py_tests', 
183                        f'beverage_can_{os.getpid()}')
184study.setDirectory(studyDir)
185study.setStudyFile(os.path.join(studyDir, 'Study_1.hstudy'))
186study.setComment("Internal math model with simple analytic formulas")
187print(f'Created study at ( {study.getStudyFile()} )')
188
189
190###############################################################################
191# Setup the nominal approach
192###############################################################################
193nom = study.getApproachList()[0]
194definition = nom.getDefinition()
195
196# Add a single Internal Math model
197modelList = definition.getModelList()
198mathModel = modelList.add("m_1", "Math Model")
199mathModel.setTool("hst_internal_math")
200mathModel.setRunScript("hst_none")
201mathModel.setHstpSchemaVersion("hstp_v_4")
202
203# Add variables, responses, goals, gradients and constraints
204addAllDesignVariables(definition.getDesignVariableList())
205addAllResponses(definition.getResponseList())
206addAllGoals(definition.getGoalList())
207addGradients(definition.getDesignVariableList(), 
208             definition.getResponseList(), 
209             definition.getGradientList())
210addVariableConstraints(definition.getVariableConstraintList())
211
212print(f"Created Nominal approach: {nom.getVarname()}")
213
214
215###############################################################################
216# Add a DOE approach
217###############################################################################
218doe = study.getApproachList().add()
219doe.setTool("Doe")
220definition = doe.getDefinition()
221mathModel.setHstpSchemaVersion("hstp_v_4")
222print(f"Created DOE approach: {doe.getVarname()}")
223
224
225###############################################################################
226# Run the DOE
227###############################################################################
228# Setup DOE specifications
229doe.setDesignVarname("Mels")
230approachTool = typing.cast(api_objects.ApproachTool_Doe, doe.getApproachTool())
231design = approachTool.getDesign()
232design.setNamedAttribute("NumRuns", 40)
233design.setNamedAttribute("SequenceOffset", 1)
234design.setNamedAttribute("Sequence", "sequence1")
235approachTool.setUncontrolledPerturbationVarname('Initial')
236
237doe.setCommandCreateDesignAPI(True)
238doe.setCommandWriteInputDecksAPI(True)
239doe.setCommandExecuteAnalysisAPI(True)
240doe.setCommandExtractResponsesAPI(True)
241doe.setCommandCreateReportAPI(True)
242doe.applySpecification()
243doe.startEvaluate()
244study.save()
245
246print(study.getStudyFile())
247studyList.closeStudy(study.getVarname())
248
249
250###############################################################################
251# Close hstbatch
252###############################################################################
253app.close()