Description of modules & subpackages¶
pyHarm is composed of multiple modules and subpackages. A user guide for the different modules and packages is provided in the links below :
Quick Walkthrough of Packages and Modules¶
In this section, the main idea behind every main package is given in order to give insights about how the packages interacts with each other.
Substructures package : This package main responsability is to create new degrees of freedom to introduce into the problem.
Elements package : This package responsability is to compute elementary residual contribution coming from linear or nonlinear forces attached to degrees of freedom.
KinematicConditions package : This package responsability is to compute the displacement of degrees of freedom that are linked through a kinematic condition, and project the possible residual contribution of the slave dofs onto the master dofs of the kinematic conditions.
Systems package : This package responsability is to make the assembly of all the residual contributions and kinematic conditions into a large full system matrix. It first expands the input displacement vector in order to take the kinematic conditions into account before computing all the elementary residuals and assembling them. A final step of projecting the kinematically constrained dofs residual and matrix reduction is made.
Reductors package : This package responsabiltiy is to introduce reduction mechanisms onto the output residual vector and jacobian matrix coming from the System before entering the solving process.
NonLinearSolver package : This package responsability is the solving of the System calling residual and jacobian matrix evaluations.
Analysis package : This package responsability is to automatize the solving of the nonlinear system for multiple System states until a stop criterion is met. For instance,in a forced response frequency analysis, the System is being solved for multiple values of input frequency in order to obtain the full forced response curve.
Predictors/Correctors/StepSizeRules package : Those package are added packages for specific FRF_NonLinear analysis.
Maestro module : This module reponsability is the reading of input dictionary, and running the analysis onto the described system. This is the main interface between pyHarm and the user.
Use the Command Line Interface (CLI)¶
pyHarm comes with a Command Line Interface (CLI) tool that gets installed at the same time as the core package. This CLI is provided to simplify the construction of a pyHarm problem and input files by providing a structure to your problem and useful tools. In this section, we will see how to use this CLI to build your problems.
The CLI code is under src/pyHarm_cli/ folder.
List of the available commands¶
The list of command with some explaination is accessible using pyharm --help.
The option --help can be added to any command as an option to display further help.
The list of commands available in the CLI is given in the next table :
Command |
Role |
Role |
|---|---|---|
|
Create a pyHarm project with intial files under the |
|
|
fill up the json file with a |
|
|
update the |
|
|
run the |
|
|
removes and clears |
|
|
exports the |
|
|
deletes the pyHarm |
|
|
adds |
|
|
removes |
Quickstart tutorial¶
pyHarm problems can be separated into two main parts. Usually, the first part involves building the system on which some analyses will be run. Secondly, the analysis gets parameterized.
Create a new pyHarm project¶
In order to create a new pyHarm project named PROJECT_NAME, one can use the command:
pyharm new PROJECT_NAME
This will create a new folder containing the main file structure that can be used for describing a pyHarm problem.
./PROJECT_NAME/
├── system.json
├── analysis.json
├── pyHarm_run.py
├── pyharm.lock
The *.json files contain the main description of a pyHarm project and will be filled later. The pyHarm_run.py is a Python script that is in charge of loading the json files and running the pyHarm project. Finally, the pyharm.lock file is a file that tracks the changes of the most important files in the project.
Build the system¶
The system.json file is supposed to hold the description of the system on which the computation will run. It contains the following keys:
key |
role |
|---|---|
system |
holds the harmonic balance characteristics |
substructures |
holds the “parts” of your whole structure |
connectors |
holds the “links” between your “parts” |
kinematics |
holds the kinematic constraints imposed on your system |
coordinates |
holds the description of local coordinate system |
Adding some substructure¶
When building the system, the first thing is usually to add the “parts” that compose it. This can be done using the command:
pyharm add PROJECT_NAME substructure
A type of ‘substructure’ selection shall appear depending on your needs; the most common is the ‘substructure’ as it can be instantiated with classical mass, rigidity matrices, etc. This should lead to:
{
"substructures": {
"substructure_00": {
"type": "substructure",
"filename": "./sub_00.mat",
"ndofs": 3
},
},
...
}
Some options are available for the command:
--optional|-o: to get optional parameters written in the json file
--type|-t: to give the type of substructure you want without interactive
--name|-n: to give a chosen name to that substructure (key of the dict)
Adding some connection between the parts¶
Once the parts are declared, you usually want to connect those. This can be done through the same command but using the connector argument:
pyharm add PROJECT_NAME connector
Remember that in pyHarm, every contributor to the residual equation is considered as a connector. Hence, forcing are declared as connectors in pyHarm – even substructures are also a type of connector (containing mass, rigidity, etc… residual contribution and Jacobian) –. Adding a LinearSpring or any other connector shall give in the system.json file:
{
...
"connectors": {
"LinearSpring_00": {
"type": "LinearSpring",
"k": 1.0,
"connect": {
"substructure_00": [
0
],
"INTERNAL": [
1
]
},
"dirs": [
0
]
}
},
...
}
connect: The key “connect” declares which substructures are linked and which nodes (for NodeToNodeElements). The first key is the name of the slave substructure, while the second key is the name of the master substructure. If you add a connection inside a substructure, use INTERNAL as displayed as the second key. If only one substructure is declared, it means the substructure is connected to the GROUND. The value provided shall be a list containing the node that is being connected.
dirs: The key “dirs” defines the direction the connector is connected to. If the connectors link in all directions, then you must add all the direction numbers.
Some options are available for the command:
--optional|-o: to get optional parameters written in the json file
--type|-t: to give the type of substructure you want without interactive
--name|-n: to give a chosen name to that substructure (key of the dict)
Adding some kinematic conditions between the parts¶
Kinematic conditions are another kind of condition you might want to add to your system. This can be done through the same command but using the kinematic argument:
pyharm add PROJECT_NAME kinematic
Once added, their writing is very similar to the connectors, thus will not be described further here.
Some options are available for the command:
--optional|-o: to get optional parameters written in the json file
--type|-t: to give the type of substructure you want without interactive
--name|-n: to give a chosen name to that substructure (key of the dict)
Describing the system component¶
The system is the last piece of the puzzle for your system.json file and can be added through:
pyharm add PROJECT_NAME system
This should result in the following in the system.json file:
{
"system": {
"type": "Base",
"nh": 1,
"nti": 1024
},
...
}
Summary of building the system.json file¶
Use
pyharm add PROJECT_NAME substructureto add as many parts as needed.Use
pyharm add PROJECT_NAME connectorto add as many connectors as needed (including forcing).Use
pyharm add PROJECT_NAME kinematicto add kinematic conditionsAdapt the necessary parameters.
Build the analysis¶
The analysis.json file is supposed to hold the description of the analysis that shall be run onto the system. It contains the following keys:
key |
role |
|---|---|
analysis |
holds the description of all the analysis |
export |
holds the export results option |
Add some analysis¶
Adding analysis to the analysis.json can be done by running the command:
pyharm add PROJECT_NAME analysis
The analysis dictionary will be filled with default parameters; you can add the --optional | -o option to the command line to add more advanced parameters directly into the file.
Add some results export¶
Exporting the results after computation is not automatic in pyHarm (default is not); you can add exportation parameters using:
pyharm add PROJECT_NAME export
This should give you:
{
"export": {
"status": false,
"export_path": ".",
"datetime": false
}
}
where export_path lets you choose a specific export folder, and the datetime option lets you add datetime information to your exported folder in order to keep history of previous runs.
pyHarm_run.py¶
This Python script is in charge of running the pyHarm project on the input data that is contained in your JSON files. Moreover, *.json files are sometimes not efficient to describe properly the problem – need to input some matrices – and it might be interesting to modify directly the Python scripts to add more advanced options. Feel free to modify and change the Python input_dict directly from the script.
####################### IMPORTS
import pyHarm
import json
####################### GLOBAL VARIABLES
BASE_PATH_SYS = r"./system.json"
BASE_PATH_ANA = r"./analysis.json"
######################## INPUT DICT
input_dict = dict()
########## plugin some user classes if needed
input_dict['plugin'] = [
]
########## System input
with open(BASE_PATH_SYS, 'r', encoding='utf-8') as sysjson:
input_dict = input_dict | json.load(sysjson)
########## Analysis input
with open(BASE_PATH_ANA, 'r', encoding='utf-8') as anajson:
input_dict = input_dict | json.load(anajson)
######################## BUILD AND RUN MAESTRO IF MAIN FILE RUN
if __name__ == "__main__":
maestro = pyHarm.Maestro(input_dict)
maestro.operate()
Run the pyHarm project¶
Once your problem is well defined, you can run the project using:
pyharm update PROJECT_NAME
pyharm run PROJECT_NAME
The update command is here to update the pyharm.lock file that tracks file changes. While the run command calls the pyHarm_run.py script.
Some of the pyHarm coding philosophy¶
Design pattern: Factory Design Pattern¶
The pyHarm codebase employs the Factory Design Pattern, which can be observed throughout the subpackages. This design pattern is chosen to facilitate the distribution of responsibilities among classes, resulting in reduced dependency and tighter code coupling. Although it may not always be the most efficient pattern for every scenario, it is consistently implemented in the codebase to enhance comprehension of the underlying mechanisms in pyHarm.
The Factory Design Pattern relies on abstract classes to define core interfaces for essential classes, denoted as ABC*** within each subpackage. By utilizing abstraction, the coupling is minimized, ensuring that any class dependent on an abstract-based class can function properly as long as interactions are limited to the defined abstract methods. Moreover, the pattern involves a Factory_*** responsible for creating instances of the ABC*** objects. This factory consists of a collection of available ABC*** subclasses, enabling the call of these classes. Through a factory function or class, based on the provided input, the desired object is instantiated and returned.
For more comprehensive information, please refer to the Refactoring Guru website.
Minimal example :¶
# Defines the abstract class :
from abc import ABC, abstractmethod
class ABCThing(ABC): # the abstract class defining the interfaces of the class
@property
@abstractmethod
def factory_keyword(self)->str:
# some abstract property to be defined in instantiable objects
...
@abstractmethod
def make_something(self):
# some abstract method to be defined in instantiable objects
...
class Stuff(ABCThing):
factory_keyword = 'stuff' # abstract property is defined
def make_something(self):# abstract method is defined
print('I do stuff')
class Things(ABCThing):
factory_keyword = 'things'
def make_something(self):
print('I do things')
ABCThing_dico = { # some dictionnary to pick the right object to instantiate from
'things':Things,
'stuff':Stuff
}
def factory_ABCThing(type_abcthing:str) -> ABCThing:
# some function to instantiate the objects
Instance = ABCThing_dico[type_abcthing]()
return Instance
Plugin Design¶
Throughout the pyHarm tutorials, you will encounter an implemented plugin system. This system seamlessly integrates with the factories present in each pyHarm subpackage by enabling the addition of new classes. The plugin system offers a convenient approach, empowering developers to code a new class within pyHarm and effortlessly inject it into the appropriate factory. Consequently, pyHarm can immediately utilize this new class as if it were already part of the existing sources.
Notably, the Maestro objects facilitate this process by incorporating any object declared within the input dictionary under the plugin key. This functionality significantly enhances the extensibility and adaptability of pyHarm, allowing for efficient integration of custom classes.
Minimal example in pyHarm :¶
from pyHarm.BaseUtilFuncs import pyHarm_plugin
from pyHarm.Maestrro import Maestro
from pyHarm.StopCriterion.ABCStopCriterion import ABCStopCriterion
#################### Some new class :
class NotSmartStopper(ABCStopCriterion):
factory_keyword = 'notsmart'
def getStopCriterionStatus(self,sol:SystemSolution,sollist:list,**kwargs) -> bool:
if len(sollist)>3 :
return True
else : return False
#################### When you want to add the new class more manually :
pyHarm_plugin(NotSmartStopper)
#################### When you work with the Maestro and want to use the new class :
INP = {
'analysis':{
'FRF':{
'study':'frf',
...,
'stopper':'notsmart' # not initially known factory_keyword
}
}
...,
'plugin':[NotSmartStopper] # add the new class in the right factory based on its ABC dependency
}
M = Maestro(INP)
M.operate()
Advice for problem data input in pyHarm¶
Advice for environment & problem building¶
Although pyHarm to this day does not have specific version dependencies, we advise that pyHarm is being used in a specific python environment in order to avoid any dependency problem with other libraries.
pyHarm can be used through classical python scripts, but we strongly advise to build the problem input under a jupyter notebook for greater interactivity and easier access to deaper useful objects.
Some feature to abuse¶
While trying to treat a problem, we strongly advise to start the process by buidling the system to be solved in the jupyter notebook and verify all the necessary degrees of freedom are taken into account. The dataFrame describing the degrees of freedom in the system is a great way of seeing if the kinematic conditions are applied on the right dofs and if all the necessary dofs are present. The dataFrame can be called using ABCSystem.expl_dofs which returns the dataFrame containing an explicit description of the dofs.
import pyHarm
input_dict = {
"substructures":{
"sub1":{
...
},
...
},
"connectors":{
...
},
"kinematics":{
...
}
}
M = pyHarm.Maestro(input_dict)
sys = M.system
sys.expl_dofs
The lists of ABCElements and ABCKinematics can also be verified in order to see if they match with the provided input file. You can have access to those lists through ABCSystem.LE (resp. ABCSystem.LC) for the list of elements (resp. kinematic conditions)
API links