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

pyharm new PROJECT_NAME

Create a pyHarm project with intial files under the PROJECT_NAME folder

pyharm add PROJECT_NAME WHAT_TO_ADD

fill up the json file with a WHAT_TO_ADD
WHAT_TO_ADD : {substructure|kinematic|connector|analysis|system|export}

--type : subclass of WHAT_TO_ADD
--name : name of the added WHAT_TO_ADD
--optional : write all the availabe parameters form the subclass

pyharm update PROJECT_NAME

update the pyharm.lock file in the PROJECT_NAME according to your lat changes

pyharm run PROJECT_NAME

run the pyHarm_run.py scripts in the PROJECT_NAME

--force : force the run by updating the lockfile

pyharm clear PROJECT_NAME

removes and clears _results folder generated by the export

pyharm export PROJECT_NAME EXPORT_TYPE

exports the pyHarm_run.py file into other types of running scripts (notebooks for more interactive solving)
EXPORT_TYPE : {notebook}

pyharm remove PROJECT_NAME --force

deletes the pyHarm PROJECT_NAME project

pyharm track PROJECT_NAME FILE_NAME

adds FILE_NAME as a tracked file in the pyHarm.lock (can be useful for added external input data)

pyharm untrack PROJECT_NAME FILE_NAME

removes FILE_NAME as a tracked file in the pyHarm.lock

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

  1. Use pyharm add PROJECT_NAME substructure to add as many parts as needed.

  2. Use pyharm add PROJECT_NAME connector to add as many connectors as needed (including forcing).

  3. Use pyharm add PROJECT_NAME kinematic to add kinematic conditions

  4. Adapt 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

pyHarm