Example Scenarios
=================

This guide is intended to show some common and simple operations that can be performed with inpRW. 
It will introduce you to some of the key functions, but it does not provide the full documentation 
for these functions. That information can be found in the :ref:`API Reference`.

Example input file
------------------

This example input file was created in Abaqus/CAE and will be used for all examples mentioned in 
this article. To follow along, copy the text of the input file and save it as **example_inp.inp**. 

.. raw:: html
   
   <details>
   <summary>Expand to see example_inp.inp</summary>
  
.. code-block::

    *Heading
    ** Job name: example_inp Model name: Model-1
    ** Generated by: Abaqus/CAE 2021.HF9
    *Preprint, echo=NO, model=NO, history=NO, contact=NO
    ** ----------------------------------------------------------------
    ** 
    ** PART INSTANCE: Part-1-1
    ** 
    *Node
        1,           0.,           0.,           0.
        2,           5.,           0.,           0.
        3,           0.,           5.,           0.
        4,           5.,           5.,           0.
    *Element, type=S4R
    1, 1, 2, 4, 3
    *Nset, nset=Part-1-1_shell_set, generate
    1,  4,  1
    *Elset, elset=Part-1-1_shell_set
    1,
    ** Section: shell_section
    *Shell Section, elset=Part-1-1_shell_set, material=steel
    0.5, 5
    *System
    *Nset, nset=Set-2
    2,
    *Nset, nset=Set-3
    3,
    *Nset, nset=Set-4
    2, 4
    *Nset, nset=origin
    1,
    ** 
    ** MATERIALS
    ** 
    *Material, name=steel
    *Elastic
    210000., 0.3
    ** 
    ** BOUNDARY CONDITIONS
    ** 
    ** Name: BC-2 Type: Displacement/Rotation
    *Boundary
    Set-2, 2, 2
    Set-2, 3, 3
    Set-2, 4, 4
    Set-2, 5, 5
    Set-2, 6, 6
    ** Name: BC-3 Type: Displacement/Rotation
    *Boundary
    Set-3, 1, 1
    Set-3, 3, 3
    Set-3, 4, 4
    Set-3, 5, 5
    Set-3, 6, 6
    ** Name: fix_origin Type: Displacement/Rotation
    *Boundary
    origin, 1, 1
    origin, 2, 2
    origin, 3, 3
    origin, 4, 4
    origin, 5, 5
    origin, 6, 6
    ** ----------------------------------------------------------------
    ** 
    ** STEP: Step-1
    ** 
    *Step, name=Step-1, nlgeom=YES
    *Static
    1., 1., 1e-05, 1.
    ** 
    ** BOUNDARY CONDITIONS
    ** 
    ** Name: Load Type: Displacement/Rotation
    *Boundary
    Set-4, 1, 1, 0.1
    ** 
    ** OUTPUT REQUESTS
    ** 
    *Restart, write, frequency=0
    *Output, field, frequency=1, variable=PRESELECT
    ** 
    ** HISTORY OUTPUT: H-Output-1
    ** 
    *End Step
    ** ----------------------------------------------------------------
    ** 
    ** STEP: Step-2
    ** 
    *Step, name=Step-2, nlgeom=YES
    *Static
    1., 1., 1e-05, 1.
    ** 
    ** OUTPUT REQUESTS
    ** 
    *Restart, write, frequency=0
    *Output, field, frequency=1, variable=PRESELECT
    ** 
    ** HISTORY OUTPUT: H-Output-1
    ** 
    *End Step

.. raw:: html
    
    </details>

Then run the following commands to generate the inp object::

    import inpRW
    
    inp = inpRW.inpRW('example_inp.inp', preserveSpacing=True, useDecimal=True, organize=True)
    inp.parse()

You will obviously need to know the syntax of any keywords you wish to modify or create, so you will 
need to use the `Abaqus Keywords Reference Guide <https://help.3ds.com/2022/English/DSSIMULIA_Established/SIMACAEKEYRefMap/simakey-c-ov.htm?contextscope=all&id=38209adfa4364bc6ab0bb6e8eb323815>`_ 
to find this information. 

This guide will use Python's 0-based indexing for all such numbers.


Modifying data in an existing inpKeyword block
----------------------------------------------

In this example, we will change the initial increment size in Step-1, and set the INC parameter of Step-1. We will 
use the following steps to accomplish this:

* Find the :class:`inpKeyword` blocks to modify
* Modify the appropriate fields
 
First, we will change the initial increment size in Step-1. We need to find the :class:`inpKeyword` block for Step-1. 
In general, we can use :func:`~inpRW._inpFind.Find.findKeyword` to find a specific keyword block. However, **inp** has 
two attributes which are shortcuts for locating steps. :attr:`~inpRW.inpRW.steps` is a case- and space-insensitive 
dictionary (:class:`csid`) that uses the step name as the key. :attr:`~inpRW.inpRW.kwg` is a :class:`csid` with keys 
corresponding to the unique keyword names in the input file. For example, :attr:`inp.kwg['step'] <inpRW.inpRW.kwg>`
is a list with all the \*STEP keyword blocks, in the order they appear in the input file. Thus, we can easily select 
the steps by their name or relative order using one of these two attributes.

For this example, we'll use the :attr:`~inpRW.inpRW.steps` approach. First, let's check the contents of 
``inp.steps['step-1']``::
    
    In : inp.steps['step-1']
    Out: [0, inpKeyword(name='Step', parameter=csid({' name': Step-1, ' nlgeom': YES}), data=[], path='self.keywords[16]', comments=[], suboptions=[...] Len 5)]

Since the entry contains a list with the relative step index (0) and the :class:`~inpKeyword.inpKeyword` block, 
and we want just the last item from the list (the :class:`~inpKeyword.inpKeyword` block, we can use this command::

    step1 = inp.steps['step-1'][-1]
    
Please note that :attr:`~inpRW.inpRW.steps` is a :class:`csid` with the step name as the key, and a value of 
[relative step index, parsed keyword block]. If you would prefer to reference the steps by their relative order, you 
could use the following command to retrieve the same block::

    step1 = inp.sortKWs(inp.kwg['step'])[0]

Now that we found the **step1** keyword block, we need to find the \*STATIC block in that step. Since we created 
**inp** with **organize=True**, we can find \*STATIC as the 0th suboption of **step1** since the procedure must 
immediately follow \*STEP in a valid input file. Then we merely change the 0th item of the 0th data line. 


Please recall that floating point like numbers should be specified as :class:`decimals <decimal.Decimal>` if 
:class:`inpRW` has been specified with *useDecimal* = True. We can also use :class:`~inpDecimal.inpDecimal`,
which inherits from :class:`~decimal.Decimal`, but also captures the exact string formatting of its input string.
Since *useDecimal* was set when we instanced :class:`inpRW`, we will use the following commands::

    from inpDecimal import inpDecimal
    static = step1.suboptions[0]
    static.data[0][0] = inpDecimal('0.1')

Next, we will add the "INC=1000" parameter to the **step1** :class:`inpKeyword` block. We will accomplish this by creating
a new parameter :class:`.csid` with just the new parameter, and then updating the existing parameter :class:`.csid`.
We do not want to replace the parameter field (as was proposed in a previous version of the guide), as this would break
any shared memory references to items in the parameter field.

::

    newpar = inpKeyword.createParamDictFromString(' INC=1000', ps=True)
    step1.parameter.update(newpar)

Creating a new keyword block and inserting it into the inp data structure
-------------------------------------------------------------------------

There are several steps to creating a new :class:`inpKeyword` block and inserting the block into :attr:`inp.keywords <inpRW.inpRW.keywords>`.

* Create the new :class:`inpKeyword` and populate the appropriate fields
* Find the desired location in :attr:`inp.keywords <inpRW.inpRW.keywords>` to place the new :class:`inpKeyword` block
* Insert the new :class:`inpKeyword`
* Update :attr:`inp.keywords <inpRW.inpRW.keywords>`
 
In this example, we will create a "\*OUTPUT, HISTORY" :class:`inpKeyword` block, and add a "\*ENERGY OUTPUT" suboption. 
We need to import the :mod:`inpKeyword` module to create a new :class:`inpKeyword`. We will use
:func:`makeDataList() <misc_functions.makeDataList>` to split up the output variables to multiple data lines. 
We do not need to specify :attr:`.path` for the :class:`inpKeyword`; this will be handled by :func:`inp.updateInp() <inpRW.inpRW.updateInp>`,
which will be called when we insert the keyword block.

::

    from inpKeyword import inpKeyword
    from misc_functions import makeDataList
    
    vars = ['ALLAE', 'ALLCCDW', 'ALLCCE', 'ALLCCEN', 'ALLCCET', 'ALLCCSD', 'ALLCCSDN', 'ALLCCSDT', 'ALLCD', 
    'ALLFD', 'ALLIE', 'ALLKE', 'ALLPD', 'ALLSE', 'ALLVD', 'ALLDMD', 'ALLWK', 'ALLKL', 'ALLQB', 'ALLEE', 
    'ALLJD', 'ALLSD', 'ETOTAL']
    eo = inpKeyword.inpKeyword(name='Energy Output', data=makeDataList(vars, 8))
    eo._setMiscInpKeywordAttrs()
    eo._dataParsed = True
    oh = inpKeyword.inpKeyword(name='Output', parameter=inpKeyword.createParamDictFromString(' History, Frequency=1'))
    oh._setMiscInpKeywordAttrs()
    oh._dataParsed = True
    oh.suboptions.append(eo)

Note that items for the :class:`~inpKeyword.inpKeyword` can either be specified at creation, or they can be modified afterwards.

Let's also add another keyword block; this one will modify the field controls. We'll use a different technique to
create this :class:`~inpKeyword.inpKeyword` block. We'll specify the entire keyword block as a string, and let
the :class:`~inpKeyword.inpKeyword` instance parse the string and generate the appropriate fields.

::

    controlsKwText = '''*CONTROLS, parameter=field
    ,,,1.0'''
    controlsKw = inpKeyword.inpKeyword(inputString=controlsKwText, **inp.inpKeywordArgs)

We pass ``inp.inpKeywordArgs`` to the :class:`~inpKeyword.inpKeyword` instance to ensure we use the same parsing
settings we used for the rest of the input file.

Next, we need to find the right place to insert the new keyword blocks. Let's insert it before \*END STEP in **step1**.

::

    step1 = inp.steps['Step-1'][-1] #step1 was assigned earlier in this tutorial, but it's included here for reference
    step1end = inp.findKeyword(keywordName='End Step', parentBlock=step1)[0]
    path = step1end.path
    inp.insertKeyword(controlsKw, path=path, updateInpStructure=False)
    inp.insertKeyword(oh, path=path) 

Note that :func:`~inpRW._inpFind.Find.findKeyword` returns a list, so we grab the 0th (and only item if we setup the search properly). 
Setting *parentBlock* for :func:`~inpRW._inpFind.Find.findKeyword` forces the function to check only suboptions of *parentBlock*.

Also note that :func:`~inpRW._inpMod.Mod.insertKeyword` will place the new keyword ahead of whatever was at *path*. Thus, **controlsKw**
will be in front of **step1end**, and then **oh** will be in front of **controlsKw**.

Finally, note that :func:`~inpRW._inpMod.Mod.insertKeyword` command will by default update :attr:`~inpKeyword.inpKeyword.path` of every
:class:`inpKeyword` (and their :attr:`.suboptions`) in :attr:`~inpRW.inpRW.keywords` after the location of the inserted keyword. 
This can be turned off (set the *updateInpStructure* parameter to False) if you need to insert a lot of keywords and want to 
update after inserting all keywords.

Deleting a keyword block
------------------------

Deleting a keyword block is a relatively simple process.
 
* Find the keyword block to delete
* Call :func:`~inpRW._inpMod.Mod.deleteKeyword`

For this example, we will delete the \*RESTART keyword blocks, as they are not used in this analysis, and the \*OUTPUT, FIELD 
request in the second step, as it is unnecessary.

First, we find the \*RESTART keyword blocks.

::

    rblocks = inp.findKeyword(keywordName='Restart')
    rblocks.reverse()

:func:`~inpRW._inpFind.Find.findKeyword` will return the valid blocks in the order it found them in the input file. 
Recall that :attr:`~inpRW.inpRW.keywords` inherits from the list type. If we remove an item from a list, all items after that item 
will be moved to a different index. Thus, we reverse *rblocks* so we can delete from the back of the sequence, as 
subsequent blocks to be deleted will be ahead of the previously deleted block in :attr:`~inpRW.inpRW.keywords`. 

Please note that you will normally not be able to sort a list of keywords using the default functions assorted with 
:class:`list` types if the input file was parsed with *Organize* = True. The keywords need to be sorted by their
:attr:`~inpKeyword.inpKeyword.path` attributes, and this will be too complicated for the built-in ordering functions.
:func:`~inpRW._inpR.Read.sortKWs` will sort any sequence of keywords in their proper order. :func:`~inpRW._inpFind.Find.findKeyword`
calls :func:`~inpRW._inpR.Read.sortKWs`, so the keyword list will already be in order.

We will delete the keywords by passing :attr:`.path` from a keyword block to the *path* parameter of :func:`~inpRW._inpMod.Mod.deleteKeyword`.

::

    for block in rblocks:
        inp.deleteKeyword(path=block.path, updateInpStructure=False)
    inp.updateInp()

:func:`~inpRW._inpMod.Mod.deleteKeyword` normally calls :func:`~inpRW.inpRW.updateInp` when it executes, but we turned it 
off in the loop. We manually call it after we deleted all the desired keyword blocks. Updating the inp structure can take 
a significant amount of time for a large input file, especially if it is called multiple times unnecessarily.

Finally, we repeat the same process, except we are deleting just one block, the \*OUTPUT, FIELD keyword block in the last 
step. Here are the commands for this task::

    ofblock = inp.findKeyword(keywordName='Output', parentBlock=inp.sortKWs(inp.kwg['step'])[-1])[0]
    inp.deleteKeyword(path=ofblock.path)

Writing the new input file
--------------------------

Now that we've made changes to the input file data structure, we merely need to write it out using the following command::

    inp.writeInp()

This will create a new input file using the original name, but ending with "_NEW.inp". If the original job was multiple files 
(i.e. \*INCLUDE), :func:`~inpRW._inpW.Write.writeInp` will write the data out to the same relative file structure, while 
appending :attr:`~inpRW.inpRW.jobSuffix` to each file and adjusting the \*INCLUDE keyword blocks in the parent file.

Complete Python Script
----------------------

Here are all of the python commands used in this guide in the form of a single script:
    

.. raw:: html
   
   <details>
   <summary>Expand to see example.py</summary>
    
::
    
    import inpRW
    import inpKeyword
    from inpDecimal import inpDecimal
    from misc_functions import makeDataList

    inp = inpRW.inpRW('example_inp.inp', preserveSpacing=True, useDecimal=True, organize=True)
    inp.parse()
    step1 = inp.steps['step-1'][-1]
    static = step1.suboptions[0]
    static.data[0][0] = inpDecimal('0.1')
    newpar = inpKeyword.createParamDictFromString(' INC=1000', ps=True)
    step1.parameter.update(newpar)
    vars = ['ALLAE', 'ALLCCDW', 'ALLCCE', 'ALLCCEN', 'ALLCCET', 'ALLCCSD', 'ALLCCSDN', 'ALLCCSDT', 'ALLCD',
    'ALLFD', 'ALLIE', 'ALLKE', 'ALLPD', 'ALLSE', 'ALLVD', 'ALLDMD', 'ALLWK', 'ALLKL', 'ALLQB', 'ALLEE', 
    'ALLJD', 'ALLSD', 'ETOTAL']
    eo = inpKeyword.inpKeyword(name='Energy Output', data=makeDataList(vars, 8))
    eo._setMiscInpKeywordAttrs()
    eo._dataParsed = True
    oh = inpKeyword.inpKeyword(name='Output', parameter=inpKeyword.createParamDictFromString(' History, Frequency=1'))
    oh._setMiscInpKeywordAttrs()
    oh._dataParsed = True
    oh.suboptions.append(eo)
    controlsKwText = '''*CONTROLS, parameter=field
    ,,,1.0'''
    controlsKw = inpKeyword.inpKeyword(inputString=controlsKwText, **inp.inpKeywordArgs)
    step1end = inp.findKeyword(keywordName='End Step', parentBlock=step1)[0]
    path = step1end.path
    inp.insertKeyword(controlsKw, path=path)
    inp.insertKeyword(oh, path=path)
    rblocks = inp.findKeyword(keywordName='Restart')
    rblocks.reverse()
    for block in rblocks:
        inp.deleteKeyword(path=block.path, updateInpStructure=False)
    inp.updateInp()
    ofblock = inp.findKeyword(keywordName='Output', parentBlock=inp.sortKWs(inp.kwg['step'])[-1])[0]
    inp.deleteKeyword(path=ofblock.path)
    inp.writeInp()
    
.. raw:: html
   
   </details>

Modified Input File
-------------------

Here is the new input file, with all the changes performed in this guide:

.. raw:: html
   
   <details>
   <summary>Expand to see example_inp_NEW.inp</summary>
  
::

    *Heading
    ** Job name: example_inp Model name: Model-1
    ** Generated by: Abaqus/CAE 2021.HF9
    *Preprint, echo=NO, model=NO, history=NO, contact=NO
    ** ----------------------------------------------------------------
    ** 
    ** PART INSTANCE: Part-1-1
    ** 
    *Node
          1,           0.,           0.,           0.
          2,           5.,           0.,           0.
          3,           0.,           5.,           0.
          4,           5.,           5.,           0.
    *Element, type=S4R
    1, 1, 2, 4, 3
    *Nset, nset=Part-1-1_shell_set, generate
     1,  4,  1
    *Elset, elset=Part-1-1_shell_set
     1,
    ** Section: shell_section
    *Shell Section, elset=Part-1-1_shell_set, material=steel
    0.5, 5
    *System
    *Nset, nset=Set-2
     2,
    *Nset, nset=Set-3
     3,
    *Nset, nset=Set-4
     2, 4
    *Nset, nset=origin
     1,
    ** 
    ** MATERIALS
    ** 
    *Material, name=steel
    *Elastic
    210000., 0.3
    ** 
    ** BOUNDARY CONDITIONS
    ** 
    ** Name: BC-2 Type: Displacement/Rotation
    *Boundary
    Set-2, 2, 2
    Set-2, 3, 3
    Set-2, 4, 4
    Set-2, 5, 5
    Set-2, 6, 6
    ** Name: BC-3 Type: Displacement/Rotation
    *Boundary
    Set-3, 1, 1
    Set-3, 3, 3
    Set-3, 4, 4
    Set-3, 5, 5
    Set-3, 6, 6
    ** Name: fix_origin Type: Displacement/Rotation
    *Boundary
    origin, 1, 1
    origin, 2, 2
    origin, 3, 3
    origin, 4, 4
    origin, 5, 5
    origin, 6, 6
    ** ----------------------------------------------------------------
    ** 
    ** STEP: Step-1
    ** 
    *Step, name=Step-1, nlgeom=YES, INC=1000
    *Static
    0.1, 1., 1e-05, 1.
    ** 
    ** BOUNDARY CONDITIONS
    ** 
    ** Name: Load Type: Displacement/Rotation
    *Boundary
    Set-4, 1, 1, 0.1
    ** 
    ** OUTPUT REQUESTS
    ** 
    *Output, field, frequency=1, variable=PRESELECT
    ** 
    ** HISTORY OUTPUT: H-Output-1
    ** 
    *Output, History, Frequency=1
    *Energy Output
    ALLAE,ALLCCDW,ALLCCE,ALLCCEN,ALLCCET,ALLCCSD,ALLCCSDN,ALLCCSDT
    ALLCD,ALLFD,ALLIE,ALLKE,ALLPD,ALLSE,ALLVD,ALLDMD
    ALLWK,ALLKL,ALLQB,ALLEE,ALLJD,ALLSD,ETOTAL
    *CONTROLS, parameter=field
    ,,,1.0
    *End Step
    ** ----------------------------------------------------------------
    ** 
    ** STEP: Step-2
    ** 
    *Step, name=Step-2, nlgeom=YES
    *Static
    1., 1., 1e-05, 1.
    ** 
    ** OUTPUT REQUESTS
    ** 
    *End Step


.. raw:: html
    
    </details>