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()