# Custom scripting

Python extensibility in Intersect provides a powerful and flexible framework for automating workflows, extending native functionality, and integrating custom logic directly within simulation models. By embedding Python scripts into the Field Management environment, you can programmatically interact with simulation objects, dynamically modify model behavior, and implement custom operational strategies that go beyond standard built-in capabilities.

At the core of this functionality is a seamless integration between Python and the Intersect Field Management system. Scripts operate on a structured hierarchy of nodes and fields that represent wells, completions, groups, and other flow entities. Through this interface, you can both retrieve simulation data (for example pressures, rates, or constraints) and apply changes programmatically using standard Python constructs.

Python 3 syntax is supported.

You can only use the Python scripting in fields that have a data type of `script_type` in the following nodes.

- [CustomScript](/solutions/intersect/fieldmanagementreferencemanual/customscript) calls Python scripts for one-time or repetitive tasks.
- [CustomControl](/solutions/intersect/fieldmanagementreferencemanual/customcontrol) calls Python scripts that perform a specific control procedure unconditionally, automatically, and repetitively alongside other built-in Field Management control procedures.
- [CustomAction](/solutions/intersect/fieldmanagementreferencemanual/customaction) calls Python scripts that perform a specific field action at runtime as an addition to the built-in Field Management actions and balancing actions. Use this node for tasks that need to be executed conditionally and dynamically or as a balancing action.
- [CustomVariable](/solutions/intersect/fieldmanagementreferencemanual/customvariable) calls Python scripts that can be embedded within expressions. Use this node to perform calculations or capture and reuse constants. CustomVariables are executed when they are called, for example as constraints or in expressions for further calculation or for reporting.
- [CustomScriptMgr](/solutions/intersect/fieldmanagementreferencemanual/customscriptmgr) specifies user-created modules that each contain a frequently used Python script. The custom nodes (CustomScript, CustomControl, CustomAction, CustomVariable) can then access the scripts from a single location by calling the module during a session.


This section includes:

- A description of the Intersect node and field structure, which is the foundation of custom scripting.
- A description of the common utilities that extract information from the simulator.
- Example Python scripts and how they affect simulation results.


If you are new to Python, it is recommended that you review some of the excellent tutorial information available through the external links below.

**External resources**:

- [python.org](https://www.python.org/)
- [learnpython.org](https://learnpython.org/)
- [automatetheboringstuff.com](https://automatetheboringstuff.com/)
- [Python documentation](http://docs.python.org)


**Further information**:

- [Extending the built-in functionality](/solutions/intersect/fieldmanagementcookbook/python)
- [Custom scripts in IXF](/solutions/intersect/fieldmanagementreferencemanual/workflow_examples/customscriptsixf)


**Relevant nodes**:

- [CustomScriptMgr](/solutions/intersect/fieldmanagementreferencemanual/customscriptmgr)
- [CustomScript](/solutions/intersect/fieldmanagementreferencemanual/customscript)
- [CustomControl](/solutions/intersect/fieldmanagementreferencemanual/customcontrol)
- [CustomAction](/solutions/intersect/fieldmanagementreferencemanual/customaction)
- [CustomVariable](/solutions/intersect/fieldmanagementreferencemanual/customvariable)
- [PIDControl](/solutions/intersect/fieldmanagementreferencemanual/pidcontrol)


## Nodes and fields

Nodes and fields in Field Management are analogous to keywords and keyword items in conventional simulators. You can set one or more fields in each node. All the nodes and fields are documented in the *Field Management node reference*.

The general syntax for setting the fields of nodes is:

![](/assets/fmcb_python_input_syntax_nodes_and_fields_1.0f6964f9c941567a9f9d9a349723b1020a13cc0673654b6d62ca44c8e5307748.367df99d.png)

All node names and field names use camel case (for example, 'NodeName' and 'FieldName').

Some nodes require a qualifying name because they are only meaningful within the context of another node.

For most nodes, a qualifying name is not necessary, but an instance name is:

```
Well('PROD1', Status='OPEN')
Well('PROD1').TubingHydraulicsTableDevice(ArtificialLiftQuantity = (2500, LIFT_GAS_INJECTION_RATE))
```

Some nodes require a context (a qualifying name) but they do not require an instance name.

In the case above, `PROD1` is the name of the `Well` that `TubingHydraulicsTableDevice` belongs to.

Some nodes require neither a qualifying name nor an instance name:

```
FieldManagement(VerbosityLevel=3)
```

Some nodes require both a context (a qualifying name) and an instance name:

```
Well('PROD1').Completion('Completion1', Status='OPEN')
```

In the case above, `PROD1` is the name of the `Well` that this `Completion` (called Completion1) belongs to. The qualifying name and instance name are separated by a dot.

Nodes that require qualified names can have the qualified name specified directly within the node context. For example, in the below script, Completion2 in well PROD1 can be referred to directly in the `Completion` node context with the correct qualification.

```
Completion('PROD1.COMPLETION2', Status='CLOSED', ClosureReason='UNAVAILABLE')
```

The list of nodes available in Field Management are documented in the *Field Management node reference* and their naming and qualification/context requirements are detailed in the **Node details** section within each node's description.

**Note:** If there are multiple reservoirs and networks in the Field Management session, reservoir entities (for example `Well`, `RegionList`) and network entities (for example `NetworkWellBoundary`, `NetworkPipe`) must be qualified with the reservoir/network name.

This is an example of setting fields:

```
Well('PROD1', Type='PRODUCER', Status='OPEN')
FieldManagement(VerbosityLevel=3)
```

Unlike keyword items for conventional simulators, Field Management fields have names and you can set them in any order. These four examples are equivalent:

```
Well('PROD1', Type='PRODUCER', Status='OPEN')
```

```
Well('PROD1', Status='OPEN', Type='PRODUCER')
```

```
Well('PROD1', Type='PRODUCER')
Well('PROD1', Status='OPEN')
```

```
Well('PROD1', Status='OPEN')
Well('PROD1', Type='PRODUCER')
```

If you set fields on separate lines, you must include the closing parenthesis:

```
Well('PROD1',
    Type='PRODUCER',
    Status='OPEN'
)
```

### Set fields on multiple nodes

You can set fields of multiple nodes at the same time. This example sets the *Type* and *Status* of wells `PROD1`, `PROD2`, and `PROD3`:

```
Well(['PROD1', 'PROD2', 'PROD3'],
    Type='PRODUCER',
    Status='OPEN'
)
```

You can also do this with qualified nodes. The following example closes completions 3 and 4 of `PROD1`:

```
Completion(['PROD1.COMPLETION3', 'PROD1.COMPLETION4'], Status='SHUTIN')
```

You can set the fields by specifying multiple qualifying nodes and multiple instances. This example closes completions 3 and 4 of wells `PROD1` and `PROD2`:

```
Well(['PROD1', 'PROD2']).Completion(['COMPLETION3', 'COMPLETION4'], Status='SHUTIN')
```

You can set the fields of the qualifying node at the same time:

```
Well(['PROD1', 'PROD2'], PiMultiplier=0.8).Completion(['COMPLETION3', 'COMPLETION4'], Status='SHUTIN')
```

### Use wildcards to match node names

You can use wildcards `*` to match node names. The following example sets all wells that have a name starting with '`PROD`' as producers and opens them:

```
Well('PROD*', Type='PRODUCER', Status='OPEN')
```

You can use wildcards to match all nodes. The following example opens all the wells in the simulation:

```
Well('*', Status='OPEN')
```

You can use wildcards with a qualifying node. The following example closes completion 4 of all wells that have a name that starts with '`PROD`':

```
Well('PROD*').Completion('COMPLETION4', Status='SHUTIN')
```

This example opens all the completions of all the wells:

```
Completion('*.*', Status='OPEN')
```

**Note:** All node names are case sensitive. For example, `Prod1` is different to `PROD1`. Similarly, `p*` does not match `PROD1`.

### Field types

Every field has a type as documented in the *Field Management node reference*.

For example, the node `Strategy` has a field called *BalancingAction*. This field encapsulates the balancing action of the strategy and the valid node types are documented in its *Field Management node reference* entry.

The special types have a different syntax in Python scripting compared to IXF.

| Type | Example |
|  --- | --- |
| `real_property_type` and `constraint_type` consist of a float value and a property identifier. In IXF, the value can be defined with a numerical expression. However, the use of expressions in `real_property_type` and `constraint_type` is not currently supported in Python scripting. | Well('PROD1',
 Constraints = [
 (1500, OIL_PRODUCTION_RATE),
 (500, BOTTOM_HOLE_PRESSURE)
 ]
) |
| `python_script_type` must be defined within triple quotes. | CustomVariable('random',
 Script = """
import random
result = random.random()
print("Random value picked:", result)
"""
) |


### Work with lists

Some fields require lists of the available types (denoted as [] in the field descriptions). Lists are defined within square brackets, with items separated by commas:

```
[item1, item2, ...]
```

For example:

```
Instruction('OilTarget',
    Actions = [ConfigureWellAction('Retube'), OpenWellAction('Queue1')]
)
```

You can input list items on multiple lines (this is advised for clarity), but you must make sure that there are commas between items and that there are both opening and closing parentheses and square brackets:

```
Well('PROD1',
    Constraints = [
        (1500, OIL_PRODUCTION_RATE),
        (500, BOTTOM_HOLE_PRESSURE)
    ],
    Status = 'OPEN'
)
```

You can use the following commands to manipulate lists:

| Command | Description, Example |
|  --- | --- |
| `append(item)` | Add an item to the end of the list: `Group('A').Members.append(Well('B'))` |
| `insert(index, item)` | Insert an item in the index (0-based): `Group('A').Members.insert(2, Well('B'))` |
| `prepend(item)` | Add an item to the beginning of the list: `Group('A').Members.prepend(Well('B'))` |
| `remove(item)` | Remove the first occurrence of an item from the list: `Group('A').Members.remove(Well('B'))` |
| `remove_all(item)` | Remove all occurrences of an item from the list: `Group('A').Members.remove_all(Well('B'))` |
| `remove_element(index)` | Remove an item from the index (0-based): `Group('A').Members.remove_element(3)` |
| `set_element(index, item)` | Set an item in the index (0-based): `Group('A').Members.set_element(0, Well('B'))` |


### Execute commands

You can execute all the commands that are documented in the *Field Management node reference* using Python. To execute the `set_constraint` command on the `Well` node:

```
Well('PROD1').set_constraint((15000, LIQUID_PRODUCTION_RATE))
```

**Further information**: [Field Management node reference](/solutions/intersect/ixfuserguide/ixug_fmnodereference)

**Relevant node**: Well

## Querying the simulator

In addition to [setting fields and executing commands](#nodes-and-fields) from custom scripts, you can:

- [Query fields](#query-fields)
- [Query commands and methods](#other-query-commands-and-methods)
- [Query properties of flow entities](#query-the-properties-of-flow-entities)
- [Use flow entity lists](#flow-entity-list)
- [Use additional node commands](#additional-commands)
- [Use special utilities](#special-utilities)


You can use all these elements to create custom scripts.

You can also use these in an interactive session.

You can extract the properties of special types of fields as follows:

| Type | Description | Example |
|  --- | --- | --- |
| `constraint_type` | - `.value` - the constraint value- `.id` - the property- `.setter` - the constraint setter | constraints = Well('PROD1').Constraints.get()
constraints[0].valueThe output of the above script is:1500.0This script gets the constraint property:constraints[0].idand the output is:OIL_PRODUCTION_RATEThis script gets the constraint setter:constraints[0].setterand the output is:'USER' |
| `real_property_type` | - `.value` - the constraint- `.id` - the property | rate = Well('PROD1').get_property(OIL_PRODUCTION_RATE)
rate.valueThe output of the above script is:1500.0This script gets the rate ID:rate.idand the output is:OIL_PRODUCTION_RATE |
| `time_type` | - `.days()` - the time in days | FieldManagement().CurrentTime.get().days()The output of the above script is:365.0 |


### Query fields

#### get() and set() methods

You can query any field with the `get()` method. This example script queries the *Status* field on the `Well` node:

```
Well('PROD1').Status.get()
```

The output of the above script is:

```
'OPEN'
```

This example does the same using a script:

```
CustomControl "HelloWorld" {
    Script = @{
if Well('PROD1').Status.get() == 'OPEN':
    print("Well PROD1 is currently open.")
else:
    print("Well PROD1 is currently closed.")
}@
}
```

You can set fields using the `set()` method. This example shows how to set the PI multiplier for a well:

```
old_pi = Well('PROD1').PiMultiplier.get()
new_pi = old_pi * a + b           # a and b have been calculated before

if new_pi < 0.1:
    new_pi = 0.1
Well('PROD1').PiMultiplier.set(new_pi)
```

This example shows how to get some common well fields:

```
wtype = well.Type.get()
wstat = well.Status.get()
wcnst = well.Constraints.get()
```

Some fields do not return a single property. For example, the *Constraints* field returns a vector with all the constraints on the well, including the ones that you set, and implied by balancing. Therefore, in this example, you must also use `const[0].value` to access the first constraint:

```
const = Well('PROD1').Constraints.get()
```

This example shows how to create a well list using an existing list. First, the script extracts an entity list from a group of wells, then creates a new group and sets its members to be the entity list:

```
entitylist = [Group('ALLCURRENTWELLS')]
for e in entitylist:
    Group('group_for_calibration').Members.set([e])
```

You can use the `set()` and `get()` methods with completion-level properties for query or assignment:

```
for well in Group('PROD').FlowEntities.get():
    well.Completion('completion_1').PiMultiplier.set(new_pi [0])
    well.Completion('completion_2').PiMultiplier.set(new_pi [1])
```

You can access PI multipliers of well-to-cell connections (the `WellDef` node accesses the well-to-cell properties):

```
WellDef(well.get_name()).WellToCellConnections().PiMultiplier.get()
```

In this example, the script gets the connections, which are reported as cell IDs, and completions of a well as a list:

```
for well in well_list:
    connections= WellDef(well.get_name()).WellToCellConnections().Cell.get()
    completions= WellDef(well.get_name()).WellToCellConnections().Completion.get()
```

You can also get and set the D-factor or skin of a connection In this example, the skin for all connections is set to 0:

```
conn_d_factor = WellDef('PROD4').WellToCellConnections().DFactor.get()
num_connections = len(conn_d_factor)
WellDef('PROD4').WellToCellConnections().DFactor.set([0.5]*num_connections)
WellDef('PROD4').WellToCellConnections().Skin.set([2.0]*num_connections)
```

#### get_property() method

The `get_property()` method queries the properties of an entity. This example shows how to access the gas production rate of a group of wells:

```
Group('PRODUCERS').get_property(GAS_PRODUCTION_RATE).value
```

`.value` accesses the value of the property. The following script gets the property based on the status of the wells. For every well in the list `mywells`, the script checks the status of the well and, if it is OPEN, obtains the bottom hole pressure.

```
for well in Well('*'):
    well_status = Well(well.get_name()).Status.get()
    if well_status == 'OPEN':
        curr_bhp = Well(well.get_name()).get_property(BOTTOM_HOLE_PRESSURE)
```

There is a list of all the well properties that you can query using the `get_property()` command under the `Well` node.

You can also use `get_property()` for completion-level properties:

```
Well('PROD1').Completion('completion_1').get_property(WATER_FLOW_RATE).value
```

#### get_name() and get_typeid() methods

You can use these methods for every node:

- `get_name()`: Gets the node name (for example, `Well('P1').get_name()` returns 'P1').
- `get_typeid()`: Gets the node type (for example, `Well ('P1').get_typeid()` returns 'Well').


This example uses the `get_name()` method to set the crossflow to occur in the well list:

```
CustomScript "Script" {
Script=@{
for well in Well('*'):
    currvalue = WellDef(well.get_name()).AllowCrossFlow.get()
    if WellDef(well.get_name()).AllowCrossFlow.get()!=True:
        WellDef(well.get_name()).AllowCrossFlow.set(True)
        print('changing well', well.get_name()+"'s AllowCrossFlow from",currvalue,'to',WellDef(well.get_name()).AllowCrossFlow.get())
    Well(well.get_name()).PiMultiplier.set(1.1)
}@
execute()
}
```

`Well('*')` is a wildcard that includes all the wells that exist in the simulation model when the script executes. If a well's *AllowCrossFlow* field is false, use `WellDef(well.get_name()).AllowCrossFlow =="TRUE"` to allow the crossflow for the well. The last line `Well(well.get_name()).PiMultiplier.set(1.1)` uses the `get_name()` and `set` methods to set PI multipliers for the wells. This changes the PI multiplier of the well.

To change the PI multiplier of the well completions:

```
Well(well.get_name()).Completion('completions1').PiMultiplier.set(1.2)
```

To get the reservoir node `Reservoir()`, which is under the node `ReservoirMgr()`, which is a child of the `FieldManagement()` node:

**Note:** This does not work for multiple reservoir simulations.

```
simname = Reservoir().get_name()
filename = simname + "_PIdegrade.txt"
print("Writing PI degradation de-bugging information to ", filename)
```

### Other query commands and methods

#### Grid properties

You can access grid properties using the `get_selection_property_values()` command. In this example, the script queries the oil saturation of all the cells in the reservoir and uses them to calculate the average oil saturation of the reservoir. First, the script creates a region covering all the cells in the reservoir through the add_selection command with the `CellSelectionFamily` node. Then, the script gets the oil saturation of the cell selection family through the get_selection_property_values() command, which works with the `GridPropertyMgr` node. Finally, the script calculates the average oil saturation.

```
CustomScript "Average_So" {
Script=@{
_fCellId= StructuredInfo("CoarseGrid").FirstCellId.get()
_nI = StructuredInfo("CoarseGrid").NumberCellsInI.get()
_nJ = StructuredInfo("CoarseGrid").NumberCellsInJ.get()
_nK = StructuredInfo("CoarseGrid").NumberCellsInK.get()

cellsSelected =list(range(_fCellId,(_nI*_nJ*_nK)+_fCellId))  
				
CellSelectionFamily("Res").add_selection("resCells",cellsSelected)

So = GridPropertyMgr().get_selection_property_values('Res',OIL_SATURATION).value
	
avg_so = sum(So)/len(cellsSelected)	
	
publish("Average oil saturation = %.6f" %avg_so)
}@
execute( )
}
```

#### Cell selection family

You can use scripts to create a cell selection family and get the properties of that family. This example shows how to create a cell selection family:

```
def create_csf_for_well_completions(csf_name):
    CellSelectionFamily(csf_name)
    wells = [w for w in WellDef('*')]
    well_names = [w.get_name() for w in wells]

    comp_cells = []
    for wn in well_names:
        for cid in WellDef(wn).WellToCellConnections().Cell.value:
            comp_cells.append(cid)
    CellSelectionFamily(csf_name).add_selection('_comp', comp_cells)
```

The `def` keyword defines the function. The function has an argument that is the name of the cell selection family and is a string type. `WellDef(wn).WellToCellConnections().Cell.value` iterates over all the cells that are in connection with the wells using the `WellToCellConnections` node. The script appends the IDs of all these cells to `comp_cells` (it is initialized as an empty list). The `add_selection()` command adds cells to the cell selection family. It takes two arguments: the first one is of type string and is the name of the new selection. The second argument is the list of cell IDs. You can call this function using:

```
create_csf_for_well_completions('completion_cell_region')
```

After you define a cell selection family, you can query its properties:

```
cID = CellSelectionFamily('completion_cell_region').Cells.value
cPress = GridPropertyMgr().get_selection_property_values('completion_cell_region', PRESSURE).value
cSWAT = GridPropertyMgr().get_selection_property_values('completion_cell_region', WATER_SATURATION).value
```

#### Editing cell properties

You can use the `CellPropertyEdit` node in scripts to change the properties of the cells in each time step (changes to cell property edits in Intersect are a one-time static change). If multiple changes are required, the name of the `CellPropertyEdit` must be different; in the following script, the simulation time is appended to the name of the `CellPropertyEdit` to differentiate edits made at different time steps. The edits in this example occur at the beginning of every time step. The edits use expressions that have the pressure of each cell (which changes over time). In the cell property edit, *Family* is the grid region selection family, *Name* is the name of the grid region selection within the family, *Property* is any property to which the edit is assigned, and *Expression* is an arithmetic expression for the edit:

```
CustomControl "PropertyEditForRegionFamily" {
        InitialScript=@{
expressionPERM_I = "PERM_I*0.50882*exp(0.000148*(PRESSURE-4500))"
expressionPERM_J = "PERM_J*0.50882*exp(0.000148*(PRESSURE-4500))"
expressionPERM_K = "PERM_K*0.50882*exp(0.000148*(PRESSURE-4500))"
}@

        Script=@{
t = FieldManagement().CurrentTime.get().days()
t_str = str(t).replace(".", "_")
# Create CellPropertyEdit_for initialization of perm and transmissibility 
edit1 = CellPropertyEdit("Edit_PERM" + t_str).Family.set(['CellSelection_ROCK_REGION','CellSelection_ROCK_REGION','CellSelection_ROCK_REGION'])
	    CellPropertyEdit("Edit_PERM" + t_str).Name.set(['ROCK_REGION_2', 'ROCK_REGION_2','ROCK_REGION_2'])
        CellPropertyEdit("Edit_PERM" + t_str).Property.set(['PERM_I', 'PERM_J', 'PERM_K'])
        CellPropertyEdit("Edit_PERM" + t_str).Expression.set([expressionPERM_I, expressionPERM_J,expressionPERM_K])
```

#### Querying time and date

Sometimes, a command or action must occur at a specific time. In this case, the query must specify the date or time. You can query the current date of the simulation using the `FieldManagement` node. To get the start of the simulation:

```
start_year = FieldManagement().StartYear.get()
start_month = FieldManagement().StartMonth.get()
start_day = FieldManagement().StartDay.get()

Start_Date = str(start_day) + '-' + start_month + '-' + str(start_year)
# Start_Date would be 21-JANUARY-2017 as an example
```

To get the current date:

```
Current_Date = FieldManagement().CurrentDate.get()
```

To get the current time use *CurrentTime* (in days from the start of the simulation):

```
FMcurrent_time = FieldManagement().CurrentTime.get()   
FMcurrent_days = FieldManagement().CurrentTime.get().days()
```

Another approach to compare dates is:

```
CustomScript "date_time" {
Script=@{
import datetime
current_date = datetime.datetime.now()
target_date = [23, 25]
if current_date.day == target_date[0]:
    print("current date is the target date")

}@

execute()
    
}
```

To use the `datetime` module, you must first import the `datetime` module. The `target_date` is a list of target days of each month.

You can request callbacks from within scripts. The simulator time steps to the specified time or date. The format for time callback is `set_time_callback(days)`. To request a time callback (applied to the `FieldManagement` node):

```
FieldManagement().set_time_callback(25.5)
```

The format of the date callback is `set_date_callback(day, month, year)`. To request a date callback:

```
FieldManagement().set_date_callback(28, "SEPTEMBER", 2015)
```

#### Modifying well constraints

You can use the `get_constraint()` and `set_constraint()` functions to obtain and set well constraints. This example shows how to use `set_constraint` to set well constraints. To set multiple constraints for each well:

```
all_wells = Group('FIELD').FlowEntities.get()
for well in all_wells:
    wtype = well.Type.get()
    wstat = well.Status.get()
    
    wwrate = 5000
    wgrate = 2000
    drawdown = 100
    
    if wstat == 'OPEN':
        if wtype == 'PRODUCER':
            well.set_constraint(Constraint(drawdown,DRAWDOWN_PIW))
            well.set_constraint(Constraint(50, BOTTOM_HOLE_PRESSURE))
        
        elif wtype == 'WATER_INJECTOR':
            well.set_constraint(Constraint(wwrate, WATER_INJECTION_RATE))
            well.set_constraint(Constraint(3000, BOTTOM_HOLE_PRESSURE))
            
        elif wtype == 'GAS_INJECTOR':
            well.set_constraint(Constraint(wgrate, GAS_INJECTION_RATE))
            well.set_constraint(Constraint(3000, BOTTOM_HOLE_PRESSURE))
```

`get_constraint()` returns a value of the input constraint argument. In this example, the script gets the original well constraint with `get_constraint()` and removes it with `set([])`, basically clearing the list of constraints. The script then updates the constraint with `set_constraint()`:

```
for well in well_list:
    initial_constraints = well.get_constraint(BOTTOM_HOLE_PRESSURE)
    well.Constraints.set([])
    well.set_constraint(Constraint(500, BOTTOM_HOLE_PRESSURE))
```

You can set the constraint for multiple wells at the same time:

```
Well(["T-28", "T-29"]).set_constraint(Constraint(500, TUBING_HEAD_PRESSURE))
```

#### Controlling rates and composition of fluids

You can specify the composition and rates of injected fluids. For example, in a gas injection process you can set the composition of the gas and apply it to the injectors:

```
FluidSourceExternal('FGP_gas_inj_composition',
                    Phase = 'GAS',
                    Composition=[(0.1633, TOTAL_MOLE_FRACTION[1]),
                                 (0.5990, TOTAL_MOLE_FRACTION[2]),
                                 (0.1959, TOTAL_MOLE_FRACTION[3]),
                                 (0.0411, TOTAL_MOLE_FRACTION[4]),
                                 (0.0007, TOTAL_MOLE_FRACTION[5])]
                    )

for well in Group("FGP_SGI").FlowEntities.get():
    well.Type.set('GAS_INJECTOR')
    well.Status.set('OPEN')
    well.InjectionStream.set(FluidSourceExternal('FGP_gas_inj_composition'))
```

The gas has five components and the mole fraction of each component is assigned. The *InjectionStream* field assigns the stream (with a defined or calculated composition) that will be injected in a well.

The *TargetRates* field of the `FluidStream` node assigns the target rate that the stream will aim to apportion to a particular usage:

```
FUEL_gas = 3000
FluidStream('FIELD_CONST').TargetRates.set([(FUEL_gas, GAS_FLOW_RATE)])
```

### Query the properties of flow entities

You can query properties against all the flow entities. The flow entities are:

- `Completion`
- `DynamicList`
- `DynamicRegionList`
- `FlowControlDevice`
- `FluidSourceExternal`
- `FluidSourceInternal`
- `FluidStream`
- `Group`
- `Heater`
- `NetworkChoke`
- `NetworkCompressor`
- `NetworkGasLift`
- `NetworkJunction`
- `NetworkPipe`
- `NetworkPump`
- `NetworkSinkBoundary`
- `NetworkSourceBoundary`
- `NetworkWellBoundary`
- `Region`
- `RegionList`
- `ReservoirMgr`
- `Sensor`
- `StaticList`
- `Well`
- `WellMgr`


The `get_property` command queries properties:

```
Well('PROD1').get_property(OIL_PRODUCTION_RATE).value 
group_vpr = Group('FIELD').get_property(RES_VOLUME_PRODUCTION_RATE)
group_vpr.value
```

The valid properties are listed in [Reporting property identifiers](/solutions/intersect/common/rprm_propertyids). For instance, to see the list of properties that you can query against a well, see the `get_property()` command in the `Well` node description.

**Note:** Some properties might not be available depending on the fluid description and the formulation used in the run.

**Further information**: [Reporting property identifiers](/solutions/intersect/common/rprm_propertyids)

### Flow entity list

Entity lists encapsulate a list of flow entities and are useful when processing multiple entities. Some nodes encapsulate other nodes, or in other words they are lists of other nodes:

| Node | Encapsulates |
|  --- | --- |
| `DynamicList` | Membership depends on the `InitialList`, `EntityLevel`, and `SelectionCriteria`. |
| `Group` | All the `Well` nodes under the group hierarchy. |
| `Region` | `Completion` nodes encapsulated in this region. |
| `RegionList`, `DynamicRegionList` | `Region` nodes in this region list. |
| `StaticList` | Can encapsulate any type of flow entity. |
| `Well` | All the `Completion` nodes in the well. |
| `WellMgr` | All the `Well` nodes in the reservoir. |


You can use `FlowEntities.get()` to query these lists. For the following script:

```
Group('A').FlowEntities.get()
```

the output is:

```
[Well('PA1'), Well('PA2'), Well('PA3')]
```

This example script loops through this list:

```
for well in Group('A').FlowEntities.get():
    print("Well", well.get_name(), "is", well.Status.get())
```

and the output is:

```
Well PA1 is OPEN
Well PA2 is CLOSED
Well PA3 is CLOSED
```

This example script stores and manipulates the list:

```
my_list = Group('A').FlowEntities.get()
my_list
```

and the output is:

```
[Well('PA1'), Well('PA2'), Well('PA3') ]
```

The `pop()` function pops out the last item in the list:

```
my_list.pop()
```

and as a result `Well('PA3')` is deleted and `my_list` is `[Well('PA1'), Well('PA2')]`.

This example script shows the list:

```
my_list
```

and the output is:

```
[Well('PA1'), Well('PA2')]
```

With a `for` loop:

```
for well in my_list:
    print("Well", well.get_name(), "is", well.Status.get())
```

the output is:

```
Well PA1 is OPEN
Well PA2 is CLOSED
```

**Note:** Manipulating the alias (`my_list`) does not affect the membership of the original flow entity list (`Group('A')`).

To get all the wells in a field, you can use the top-level 'FM_FIELD' in the `Group`:

```
all_wells = Group('FM_FIELD').FlowEntities.get()
```

The entity keyword can access a mixed list of flow entities. In this example, the entity list is a dynamic list consisting of wells and completions (some wells have more than one completion) that shuts in high GOR entities (wells and/or completions). If after closing the well or completion the production rate is below the target, it re-opens them:

```
CustomAction "CloseWellorCompletionAction" {
        ScriptRequiresEntity = TRUE
        EntityList = DynamicList('high_gor_mix_entities')
        Script = @{
print("Production before:", Group('PRODUCERS').get_property(GAS_PRODUCTION_RATE).value)
print("Closing:", entity.get_name())

if "Completion" in entity.get_typeid():
    entity.Status.set("CLOSED")
else:
    entity.Status.set("ALL_COMPLETIONS_SHUTIN")
entity.ClosureReason.set("USER")

print("Production after :", Group('PRODUCERS').get_property(GAS_PRODUCTION_RATE).value)

if Group('PRODUCERS').get_property(GAS_PRODUCTION_RATE).value < float(Expression('FacilityGasCapacity').Definition.get()):
    print("Closing caused below target, undo:",
               entity.get_name())
    if "Completion" in entity.  get_typeid():
        entity.Status.set("OPEN")
    else:
        entity.Status.set("OPEN")
    entity.ClosureReason.set("NONE")
}@
```

The *ScriptRequiresEntity* field is set to true because the custom action uses a script that makes use of 'entity' to execute operations on different entities provided as a flow entity list. The *EntityList* field specifies the list of entities that the entity will use later. The *EntityList* can be a group, a static list, or a dynamic list (one of the entities listed previously). The `entity.get_name()` command gets the name and the `entity.get_typeid()` command gets the entity type.

### Additional commands

There are several additional commands on some nodes that you can use in custom scripts.

Some of these commands require arguments. The arguments are listed as:

`name` (*in/out/update* Type) where:

- `name` is the name of the argument.
- *in/out/update* is one of: *in* if the argument is input only, *out* if the argument is output only, and *update* if the argument is both input and output.
- Type is the argument type.


**Note:** Backward compatibility of these commands is not guaranteed.

#### Well: get_ipr_table(ipr_table, number_of_points, pressure_range, skewness_factor, region)

This command generates an IPR table for the current state of the model for the given bottom hole or tubing head pressure points.

The command takes five arguments:

1. `ipr_table` (*update* real_vector_property_type[]): The list of rate properties to include in the IPR. Output the full table of pressures and rates.
2. `number_of_points` (*in* Integer): The number of pressure points to generate.
3. `pressure_range` (*in* real_vectory_property_type): The minimum and maximum pressure point in the table. The property ID also determines the pressure location (bottom hole or tubing head).
4. `skewness_factor` (*in* Double): The skewness factor to determine the distribution of the pressure points. This must be greater than 0.0.
5. `region` (*in* Region) A region around this well. If specified, the domain around this well is solved to generate the IPR. Use `None` for no domain solves.


```
ipr = Well('PA1').get_ipr_table([BOTTOM_HOLE_PRESSURE, OIL_PRODUCTION_RATE, GAS_PRODUCTION_RATE], 5, ([100, 1000], BOTTOM_HOLE_PRESSURE), 1.0, None)
```

The return value in this example `ipr[*column*].value[*row*]` encapsulates the IPR table whose elements you can access with a zero-based row and column index. For example:

```
print(ipr)
print("Oil rate at BHP of", ipr[0].value[1], "is", ipr[1].value[1])
print_ipr(ipr)
```

produces the following output:

```
[ixworld.DoubleVectorProperty([100, 325, 550, 775, 1000], BOTTOM_HOLE_PRESSURE), ixworld.DoubleVectorProperty([106134.36671718997, 97909.53417091204, 89684.7016246374, 81459.86907836012, 73235.03653208526], OIL_PRODUCTION_RATE), ixworld.DoubleVectorProperty([1436198.9039730942, 1324901.3492441988, 1213603.7945152994, 1102306.2397863993, 991008.6850574982], GAS_PRODUCTION_RATE)]

Oil rate at BHP of 325.0 is 97909.5341709

|   | BOTTOM_HOLE_PRESSURE | OIL_PRODUCTION_RATE | GAS_PRODUCTION_RATE |
------------------------------------------------------------------------
| 1 | 100.0                | 106134.366717       | 1436198.90397       |
| 2 | 325.0                | 97909.5341709       | 1324901.34924       |
| 3 | 550.0                | 89684.7016246       | 1213603.79452       |
| 4 | 775.0                | 81459.8690784       | 1102306.23979       |
| 5 | 1000.0               | 73235.0365321       | 991008.685057       |
```

The following custom script writes the IPR table of all the wells into a file:

```
CustomScript "get_IPR_table" { 
        ExecutionPosition = BEGIN_TIMESTEP

        Script=@{
Num_dataPoints = 10
Table_Header = [BOTTOM_HOLE_PRESSURE, OIL_PRODUCTION_RATE, GAS_PRODUCTION_RATE] 

for well in Well('*'):
    if well.Status.get() == 'OPEN':
        res_press = well.get_property(PRESSURE).value
        min_press = res_press - 1000
        ipr = well.get_ipr_table(Table_Header, Num_dataPoints,
                    ([min_press, res_press], BOTTOM_HOLE_PRESSURE),1.0, None) 
    print(well)
    print("Gas rate at BHP of", ipr[0].value[1], "is", ipr[2].value[1])
    print_ipr(ipr)              
}@
}
```

`ipr[0]` has the bottom hole pressures, `ipr[1]` has the oil production rates, and `ipr[2]` has the gas production rates.

#### RegionList: get_inter_region_properties(properties, source_region_name, target_region_name, flow_type)

This command gets a set of inter-region properties for the regions in this list.

The command takes four arguments:

1. `properties` (*update* real_property_type[]): A set of property vectors. Populated with values on return. The property can be one of: `BRINE_FLOW_CUML`, `BRINE_FLOW_RATE`, `COMPONENT_FLOW_CUML`, `COMPONENT_FLOW_RATE`, `GAS_FLOW_CUML`, `GAS_FLOW_RATE`, `OIL_FLOW_CUML`, `OIL_FLOW_RATE` , `SOLVENT_FLOW_CUML`, `SOLVENT_FLOW_RATE`, `POLYMER_FLOW_RATE`, `WATER_FLOW_CUML`, `WATER_FLOW_RATE`.
2. `source_region_name` (*in* String): Name of the source region.
3. `target_region_name` (*in* String): Name of the target region.
4. `flow_type` (*in* String): The type of flow rate to return. One of: NET, IN, OUT.


The following queries the oil and gas flow rates between regions `A` and `B` in the region list named `partition`:

```
ab_net = RegionList('partition').get_inter_region_properties([OIL_FLOW_RATE, GAS_FLOW_RATE], 'A', 'B', 'NET')
```

The return value in this example `ab_net` is a list of `real_property_type`. For example:

```
print(ab_net)
for i in range(len(ab_net)):
print("Net {} from region A to B is {}".format(ab_net[i].id.get_name(), ab _net[i].value))
```

produces the following output:

```
[ixworld.DoubleProperty(234.37831628531484, OIL_FLOW_RATE), ixworld.DoubleProperty(929.473315509299, GAS_FLOW_RATE)]

Net OIL_FLOW_RATE from region A to B is 234.378316285
Net GAS_FLOW_RATE from region A to B is 929.473315509
```

### Special utilities

There are a number of utilities that you can use in scripts.

| Utility | Description |
|  --- | --- |
| `ixhelp()` | Enter online help. |
| `help(...)` | Open Python language help, for instance to get help for the module `sys`: `help(sys)` |
| `help()` | Call with no arguments to enter built-in Python language online help. |
| `print_node(Well('PROD1'))` | Print the fields and commands of a node. |
| `print_tree()` | Print all the nodes in the current session. |
| `print_tree(Well('PROD1'))` | Print all the nodes starting from the specified node. |
| `print_wells_info()` | Print type and constraints of all wells in the session. |
| `print_table(node_list, attr_list)` | See details below. |
| `print_ipr(ipr)` | Print the IPR table generated with the `Well get_ipr_table` command. |
| `exit()` | Stop the simulation immediately. Note that the simulation reporting files may not be closed properly. Use the Field Management shutdown command to stop the simulation correctly. |
| `execfile('my_commands.py')` | Execute the contents of the file with the given name. |


#### print_table

The table printing utility `print_table` takes two arguments:

- `node_list`: The list of nodes to print (one row for each node in the list).
- `attr_list`: The list of attributes to print, that is function names callable on a node ('name') and/or properties (for example `OIL_PRODUCTION_RATE`).


For instance, the following example

```
print_table([Well('PROD1'), Well('INJ1')], ["name", OIL_PRODUCTION_RATE])
```

results in:

```
+-------------------------------------+
| |   | name  | OIL_PRODUCTION_RATE | |
| ----------------------------------- |
| | 1 | PROD4 | 146.3751678595806   | |
+ ----------------------------------- +
```

The following example prints the information on Group A and all wells in this group:

```
print_table([Group('A')]+Group('A').FlowEntities.get(), ["name", OIL_PRODUCTION_RATE])
```

This results in:

```
+-------------------------------------+
| |   | name  | OIL_PRODUCTION_RATE | |
| ----------------------------------- |
| | 1 | P     | 406.8333541939952   | |
| | 2 | PROD4 | 146.3751678595806   | |
| | 3 | PROD5 | 121.72739022947032  | |
| | 4 | PROD2 | 68.97656871187823   | |
| | 5 | PROD3 | 69.75422739306602   | |
+-------------------------------------+
```

## Extending Field Management functionality

### Use third-party Python packages

You can use third-party Python packages (Numpy, for example) by installing them as site packages within the Intersect Python configuration. You can install a recommended set of packages using the Intersect setup and selecting the 'Install custom Python packages' option. This will install the following packages: Numpy, Scipy, Pandas, SpotFire, Tabulate, and Scikit-learn. The Python Pip package installer downloads these packages from the [PyPI repository](https://pypi.org/project/pip/). You must have an internet connection so that Pip can connect to PyPI. This set of packages is also installed in the Delfi environment.

You can install additional packages by running Pip.

1. Locate the Python executable installed with Intersect.
For a Windows installation, if you are using the IX Intel® MPI version, the default install location is `C:\ecl\VERSION\IX\x64_ilmpi\python\python.exe`
For a Linux installation, if you are using the IX Intel MPI version, the default install location is `ix/linux_x86_64_ilmpi/python/bin/python3`.
2. Open a command prompt in the Python executable directory and run `pip install` with the name of your desired package.
For example, `.\python -m pip install numpy` on Windows or `./python3 -m pip install numpy` on Linux downloads and installs the latest version of Numpy.
Packages are installed into the site-packages sub-directory under the IX Python directory.
3. Use the package in your Python script by importing it in the usual way, for example `import numpy`.


**Note:** You can also use Pip to update or delete installed packages.

### Report the completion and cumulative production status

You can use custom scripts to generate a report of the completion status and cumulative production of each well at the end of each time step.

```
START

CustomControl "CompletionCum_report" {

ExecutionPosition=END_TIMESTEP

InitialScript=@{
import csv

_filename = Reservoir().get_name() + "_CMPL_CUM_PROD_REPORT.csv"

_file = open(_filename, 'w', newline='' )

writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)

writer.writerow(['Time', 'Well', 'Completion', 'Status', 'OilCum', 'GasCum', 'WaterCum'])

print("Writing the completion cumulative production status report to", _filename)

}@

Script=@{
current_time = "%s" %(FieldManagement().CurrentTime.get())
first = True

for well in Well('*'):
    if well.Type.get()=='PRODUCER':
        if first:
            first = False
            writer.writerow([current_time, well.get_name()])
        else:
            writer.writerow(['-', well.get_name()])
            
        for completion in well.FlowEntities.get():
            writer.writerow(['-', '-', completion.get_name(), completion.Status.get(), completion.get_property(OIL_PRODUCTION_CUML).value, completion.get_property(GAS_PRODUCTION_CUML).value, completion.get_property(WATER_PRODUCTION_CUML).value])

        if first:
            writer.writerow(["No recompleted wells"])
}@

FinalScript=@{
_file.close()

print("Completion cumulative production status report written to", _filename)

}@
    
}
```

The name of the output file and the table headers for the completion status and cumulative production are defined under the initial script. Then, the main script gets the completion status and the cumulative production at the end of each time step. Finally, the final script closes the output file and prints a notice to the PRT file after the execution of the custom script.

After the execution of the custom script, the report of completion status and cumulative production for each well is written to the CSV file, as shown in [Figure 1](#python-extending-completioncumul-fig1).

![](/assets/python_completioncumul.44e31b771fae872ab00a63033836f68887b889179bdf6c10fd044ea24a31dd2d.367df99d.png)

### Calculate the PI for injection wells

You can use a custom script to calculate the PI for injection wells at the end of each time step.

```
START

CustomControl "cal_ProdWellPI" { 
    ExecutionPosition = END_TIMESTEP
    InitialScript = @{
import csv
_filename = Reservoir().get_name() + "_Prod_PI.csv"
_file = open(_filename, 'w')
writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
writer.writerow(['Well Name','Time','cal_DDW'])
}@
    Script = @{
for well in Well('*'):  
    if well.Type.get() == 'PRODUCER' :
        well_Prod = Well(well.get_name()).get_property(LIQUID_PRODUCTION_RATE).value
	  well_DD = Well(well.get_name()).get_property(DRAWDOWN).value

	  well_PI = well_Prod / well_DD
 
        Time = FieldManagement().CurrentTime.get()
        print("Printing PI weighted drawdown for well:  " + well.get_name() + " to file")
        writer.writerow([well.get_name(), Time, well_PI])
}@
    FinalScript = @{
_file.close()
}@
}
```

First, define the name of the custom script:

```
CustomControl "cal_ProdWellPI" {
```

Define the execution position of the custom script:

```
    ExecutionPosition = END_TIMESTEP
```

The initial custom script defines the name of the output file and the table header for the well name, output time, and calculated PI:

```
   InitialScript = @{
import csv
_filename = Reservoir().get_name() + "_Prod_PI.csv"
_file = open(_filename, 'w')
writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
writer.writerow(['Well Name','Time','cal_DDW'])
}@
```

The main custom script calculates the transmissibility, drawdown pressure, and PI for injection wells at the end of each time step:

```
Script = @{
for well in Well('*'):  
    if well.Type.get() == 'PRODUCER' :
        well_Prod = Well(well.get_name()).get_property(LIQUID_PRODUCTION_RATE).value
	  well_DD = Well(well.get_name()).get_property(DRAWDOWN).value

	  well_PI = well_Prod / well_DD

        Time = FieldManagement().CurrentTime.get()
        print("Printing PI weighted drawdown for well:  " + well.get_name() + " to file")
        writer.writerow([well.get_name(), Time, well_PI])
}@
```

The final custom script closes the output file:

```
   FinalScript = @{
_file.close()
}@
}
```

After the execution of the custom script, the PI of the injection well at the end of each time step is generated in a CSV file, as shown in [Figure 2](#python-extending-picalc-fig1).

![](/assets/python_picalc.93e67fefb1772a2322205ade9bfcefcd1e3e720efdf090163345ca4fbe505922.367df99d.png)

### Output a customized table at every time step

You can generate customized reports by querying any of the available fields and output them to the screen or output file.

```
CustomControl "info" {
    Script = @{
print("Outputting some useful information for well PROD1")
print("  Current time   :", FieldManagement().CurrentTime.get())
print("  Status         :", Well('PROD1').Status.get())
print("  Closure reason :", Well('PROD1').ClosureReason.get())
}@
}
```

The script above executes at the beginning of every time step and produces an output similar to the following:

```
...
REPORT   The custom script generated the following messages:
         +---------------------------------------------------+
         | Outputting some useful information for well PROD1 |
         |   Current time   : 0.0                            |
         |   Status         : OPEN                           |
         |   Closure reason : NONE                           |
         +---------------------------------------------------+
...
```

By default, `CustomControl` is executed at every `BEGIN_TIMESTEP` position. You can modify this execution position by setting the *ExecutionPosition* field. For instance, it generally makes more sense to report on the converged state of the reservoir, rather than the state at the beginning of the time step. In this case, you must set this field to `END_TIMESTEP`:

```
CustomControl "info" {
    ExecutionPosition = END_TIMESTEP
}
```

You can also write the information to a file:

```
CustomControl "info_to_file" {
    InitialScript = @{
import time
print("Outputting information to my_info_file.txt")
file = open('my_info_file.txt', 'w')
print("This file was generated on", time.ctime(), file=file)
}@
    Script = @{
print("Outputting some useful information for well PROD1", file=file)
print("  Current time   :", FieldManagement().CurrentTime.get(), file=file)
print("  Status         :", Well('PROD1').Status.get(), file=file)
print("  Closure reason :", Well('PROD1').ClosureReason.get(), file=file)
}@
    FinalScript = @{
print("Done, closing file")
file.close()
}@
}
```

The file `my_info_file.txt` will contain output similar to the following:

```
This file was generated on Tue Aug 25 17:12:11 2009
Outputting some useful information for well PROD1
  Current time   : 0.0                              
  Status         : OPEN                             
  Closure reason : NONE                            
Outputting some useful information for well PROD1
  Current time   : 2.0                              
  Status         : SURFACE_ABANDONED
  Closure reason : USER
...
```

### Output the reservoir pressures around wells

You can create on-demand regions around wells (refer to Create on-demand regions around wells at run time). The created pressures from the regions are corrected to the bottom hole reference depth of the corresponding well.

The following custom control outputs reservoir pressures for these regions to a CSV file for every time step:

```
CustomControl "Pres_around_wells" {
ExecutionPosition=END_TIMESTEP
InitialScript=@{
filename=Reservoir().get_name() + "_domain_pressure.csv"

wellnames = Well('*').get_name()

[RegionMgr().create_regions_around_wells(region_list_name='domains'+i, wells=[Well(i)], cell_hops=1) for i in wellnames]

import csv

_file = open(filename, 'w', newline='')
writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)

header=["DATE","TIME"]

for well in Group('FM_FIELD').FlowEntities.get():
	header.append(well.get_name())
	
writer.writerow(header)

}@


Script=@{
data = []

data.append(FieldManagement().CurrentDate.get())
data.append(FieldManagement().CurrentTime.get())

for well in Group('FM_FIELD').FlowEntities.get():
	data.append(RegionList('domains' + well.get_name()).get_property(PRESSURE).value)

writer.writerow(data)
_file.flush()
}@

FinalScript=@{
_file.close()
print("File", filename, "written.")
}@
    
}
```

You can open the resulting CSV file in Microsoft Excel:

![](/assets/fmcb_extending_the_built-in_fm_functionality_output_reservoir_pressures_around_wells.d9daf55ae53161f7a2e035593daf962b97960b534917af9bf9d3a9eca4533d43.367df99d.png)

**Further information**: Create on-demand regions around wells at run time

### Write an IPR table to a CSV file

You can use a script to report a well IPR table.

```
START

CustomControl "get_IPR_table" {
    ExecutionPosition = END_TIMESTEP
    InitialScript=@{
import csv
_filename = Reservoir().get_name() + "_IPR.csv" 
_file = open(_filename, 'w', newline='') 
writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
Num_dataPoints = 30  
Table_Header = [BOTTOM_HOLE_PRESSURE, OIL_PRODUCTION_RATE, GAS_PRODUCTION_RATE]
}@
    Script=@{
res_press = Well("PROD4").get_property(PRESSURE).value
min_press = 14.7 
ipr = Well("PROD4").get_ipr_table(Table_Header, Num_dataPoints, ([min_press, res_press], BOTTOM_HOLE_PRESSURE), 1.0, None)
writer.writerow(["IPR Table for Well:", Well("PROD4").get_name(), FieldManagement().CurrentTime.get()])
writer.writerow(["Data Point", Table_Header[0], Table_Header[1] ,Table_Header[2]])
for i in range(Num_dataPoints):
    writer.writerow([i+1, ipr[0].value[i] ,ipr[1].value[i] ,ipr[2].value[i]])  
writer.writerow([" "])
}@
    FinalScript=@{   
_file.close()
}@
}
```

Define the name of the script:

```
CustomControl "get_IPR_table" {
```

Specify the execution position of the IPR table:

```
    ExecutionPosition = END_TIMESTEP
```

The initial script defines the output file name, number of data points, and table headers for the IPR table:

```
   InitialScript=@{
import csv
_filename = Reservoir().get_name() + "_IPR.csv" 
_file = open(_filename, 'w', newline='') 
writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
Num_dataPoints = 30  
Table_Header = [BOTTOM_HOLE_PRESSURE, OIL_PRODUCTION_RATE, GAS_PRODUCTION_RATE]
}@
```

The main script defines the minimum pressure for the IPR table, gets the reservoir pressure at the well, generates the IPR datasets using the `get_ipr_table ()` command in the script, and prints the IPR table in the file specified in the initial script:

```
   Script=@{
res_press = Well("PROD4").get_property(PRESSURE).value
min_press = 14.7 
ipr = Well("PROD4").get_ipr_table(Table_Header, Num_dataPoints, ([min_press, res_press], BOTTOM_HOLE_PRESSURE), 1.0, None)
writer.writerow(["IPR Table for Well:", Well("PROD4").get_name(), FieldManagement().CurrentTime.get()])
writer.writerow(["Data Point", Table_Header[0], Table_Header[1] ,Table_Header[2]])
for i in range(Num_dataPoints):
    writer.writerow([i+1, ipr[0].value[i] ,ipr[1].value[i] ,ipr[2].value[i]])  
writer.writerow([" "])
}@
```

The final script closes the file:

```
   FinalScript=@{   
_file.close()
}@
}
```

[Figure 3](#python-extendingiprtocsv-fig1) shows the IPR data at day 1 of production. The datasets are printed in a CSV file.

![](/assets/python_iprdata.3b939355ef88b34757f385204bd2841e22d183b3a0e68904bdf7395be318c96c.367df99d.png)

### Implement a customized acidization action

You can approximate the effects of well acidization by modifying the PI of a well. These empirical formulas determine the new PI of the well, based on the amount and type of some new types of acidizing fluid:

Fluid type A
:   PImultiplier = 1 + qacid//2000 with a maximum of 1.5

Fluid type B
:   PImultiplier = 1 + qacid//2500 with a maximum of 1.75

![](/assets/fmcb_extending_the_built-in_fm_functionality_implementing_a_customized_acidization_action_1.cf56ba522bbce716ef02ecce78607c66433e2d7a571d4888b55e808d0e7e0c39.367df99d.png)

Although Field Management does have a built-in action to modify well PI (`SetFieldAction`), there is no built-in functionality to entirely encapsulate these empirical formulas. So, you must create a new custom action:

```
CustomAction "acidize" {
    ScriptIsBalancing = FALSE
    ScriptIsTopologyModifying = TRUE
    ScriptRequiresEntity = TRUE
    ArgNames    = [
        "Fluid type"
        "Acid volume"
    ]
    ArgDefaults = [
        "A"
    ]
    ArgTypes    = [
        "string"
        "double"
    ]
    ArgComments = [
        "The fluid type to use. Type A: 1 + qacid/2000 with a maximum of 1.5 Type B: 1 + qacid/2500 with a maximum of 1.75."
        "The volume of acid to use."
    ]
    Script = @{
if '$arg1' == 'A':
    print("Using fluid type A")
    pi_mult = min(1 + $arg2/2000, 1.5)
else:
    print("Using fluid type B")
    pi_mult = min(1 + $arg2/2500, 1.75)

print("Setting a PI multiplier of", pi_mult, "on", entity)
entity.PiMultiplier.set(pi_mult)
}@
}
```

The fields *ScriptIsBalancing*, *ScriptIsTopologyModifying*, and *ScriptRequiresEntity* are set so that the custom action is evaluated correctly within the action lists in the instruction.

This sets up the new acidization action for inclusion in Field Management strategies:

```
DynamicList "unacidized" {
    InitialList = Group('FIELD')
    EntityLevel = WELL
    SelectionCriteria = [
        "PiMultiplier == 1"
    ]
}

CustomAction "acidize" {
    EntityList = DynamicList('unacidized')
    Args = ["B" "1200"]
}

Instruction "gas-target" {
    TriggerCriterion = "OIL_PRODUCTION_RATE(Group('FIELD')) < 100000"
    Actions = [
        CustomAction('acidize')
    ]
}
```

Custom actions can make use of resources (including rigs), just like any other action. The following sets up the acidization action, which takes up three days of rig time and is limited by the amount of total acid volume available for the field:

```
AmountResource "fluid-A" {
    AmountAvailable = 5000
}
AmountUsage "fluid-A-usage" {
    AmountResources = [
        AmountResource('fluid-A')
    ]
    Usage = 1200
}

RigResource "rig1" {
}
RigResource "rig2" {
}
RigUsage "rig-acidization-usage" {
    WorkoverTime = 3
    RigResources = [
        RigResource('rig1')
        RigResource('rig2')
    ]
}

CustomAction "acidize" {
    Args = [
        "A"
        "1200"
    ]
    ResourceUsages = [
        AmountUsage('fluid-A-usage')
        RigUsage('rig-acidization-usage')
    ]
}
```

All rig activities other than drilling are considered as workover activities.

### Approximate geomechanical effects of decreasing well pressure

The dropping pressure around the wellbore may lead to compaction of the rock, which in turn may lead to decreased productivity. Consider the following empirical formula:

PImultiplier = 1 + c(Pi-Pbh)

where

|c|is a constant,|
|Pi|is the initial bottom hole pressure, and|
|Pbh|is the current bottom hole pressure.|

The following custom control updates the well's PI multiplier based on the pressure drop from the initial pressure at every time step:

```
CustomControl "wellbore-collapse" {
    InitialScript = @{
import math

# variables
c = -1.0/100000
wells = Group('producers').FlowEntities.get()

# internal, to keep track of the changing pressure
initial_pressure = None
}@
    Script = @{
if initial_pressure == None:
    initial_pressure = {}
    for well in wells:
        initial_pressure[well] = well.get_property(BOTTOM_HOLE_PRESSURE).value

for well in wells:
    current_pressure = well.get_property(BOTTOM_HOLE_PRESSURE).value
    pi_mult = math.exp(c*(initial_pressure[well] - current_pressure))
    well.PiMultiplier.set(pi_mult)
    publish('Setting PI multiplier of well ' + well.get_name() + ' to ' + str(pi_mult))
}@
}
```

### GAP OpenServer commands in custom scripts

You can use all of the available GAP OpenServer commands within custom scripts with the `execute_command` method on `Network`. This enables you to encapsulate entire OpenServer-based logic within Field Management or simply to set up conditional OpenServer commands.

For instance, the following custom action modifies the flow correlation to use on a pipe in the GAP network when the field rate falls below 10,000 STB/d:

```
CustomAction "SetCorrelation" {
    ScriptIsBalancing = FALSE
    ScriptIsTopologyModifying = TRUE
    ScriptRequiresEntity = FALSE
    Script = @{
Network().execute_command("DoSet GAP.MOD[{PROD}].PIPE[{MainPipeOut}].PIPECORR BeggsandBrill")
}@
}

Instruction "SetCorrelation" {
    TriggerCriterion = "OIL_PRODUCTION_RATE(Group('FIELD')) < 10000"
    Actions = [
        CustomAction('SetCorrelation')
    ]
}

Strategy "FieldStrategy" {
    Instructions = [
        Instruction('SetCorrelation')
    ]
}
```

The scripts can also make use of the return values of the `DoGet` commands. For example, the following custom script sets the flow correlation according to whether the flow in a pipe is in the tubing or the annulus:

```
CustomScript "TubingVsAnnular" {
    Script = @{
returns = Network().execute_command_return("DoGet GAP.MOD[{PROD}].PIPE[{MainPipeOut}].FlowType", "", "")

command_output = returns[0][0]
command_error = returns[0][1]

if len(command_error):
    print("GAP has returned an error", command_error)
else:
    print("GAP returned flow type", command_output)
    if command_output == 'TUBING':
        print("Setting correlation to BeggsandBrill")
        Network().execute_command("DoSet GAP.MOD[{PROD}].PIPE[{MainPipeOut}].PIPECORR BeggsandBrill")
    elif command_output == 'ANNULAR':
        print("Setting correlation to DunsandRosModified")
        Network().execute_command("DoSet GAP.MOD[{PROD}].PIPE[{MainPipeOut}].PIPECORR DunsandRosModified")
    else:
        print("Unknown flow type")
}@
    execute()
}
```

This generates output similar to the following:

```
INFO     GAP OpenServer command "DoGet GAP.MOD[{PROD}].PIPE[{MainPipeOut}].FlowType" returned:
           TUBING
REPORT   CustomScript:TubingVsAnnular script generated the following messages:
         +--------------------------------------+
         | GAP returned flow type TUBING        |
         | Setting correlation to BeggsandBrill |
         +--------------------------------------+
```

**Relevant node**: Network

### Share utility methods among scripts with modules

You can register your own Python modules in the Field Management system so that they are available to all the scripts within the Field Management extensibility points. This is useful in cases where there are common utilities that multiple scripts must access.

The following module called `myutils` defines a print method and a utility method that calculates the radius of a cylinder-shaped region around a well:

```
CustomScriptMgr {
    ModuleNames = [
        "myutils"
    ]
    ModuleScripts = [
@{
import math

# PiMultiplier multiplier
_pi_mult_mult = 0.95

def print_info(well):
    print("   Status =", well.Status.get())
    print("  PI-mult =", well.PiMultiplier.get())
    if well.Type.get() == 'PRODUCER':
        print("     qoil =", well.get_property(OIL_PRODUCTION_RATE).value)
        print("     qgas =", well.get_property(GAS_PRODUCTION_RATE).value)
        print("     qwat =", well.get_property(WATER_PRODUCTION_RATE).value)
    elif well.Type.get() == 'WATER_INJECTOR':
        print("    qwati =", well.get_property(WATER_INJECTION_RATE).value)
    elif well.Type.get() == 'GAS_INJECTOR':
        print("    qgasi =", well.get_property(GAS_INJECTION_RATE).value)

def calculate_radius(pore_volume, height, porosity):
    return (pore_volume/(math.pi*height*porosity))**0.5
}@
    ]
}
```

You can then access all these methods from all scripts within the session:

```
CustomAction "decrease_productivity" {
    InitialScript = @{
import myutils
_wells = Well('P*')
}@
    Script = @{
for well in _wells:
    print("Well", well.get_name())
    print("Before:")
    myutils.print_info(well)
    #
    new_value = well.PiMultiplier.get() * myutils._pi_mult_mult
    well.PiMultiplier.set(new_value)
    #
    print("After:")
    myutils.print_info(well)
}@
}

CustomControl "water_radius" {
    ExecutionPosition = END_TIMESTEP
    InitialScript = @{
import myutils
_well = Well('INJ1')
_porosity = Region('RESERVOIR.RESERVOIR').get_property(POROSITY).value

completions = _well.FlowEntities.get()
top_depth = completions[0].get_property(DEPTH).value
bottom_depth = completions[-1].get_property(DEPTH).value

_height = bottom_depth - top_depth
print("Approximate column height for well", _well.get_name(), "is", _height)
}@
    Script = @{
myutils.print_info(_well)

cuml_vol_inj = _well.get_property(RES_VOLUME_INJECTION_CUML).value
radius = myutils.calculate_radius(cuml_vol_inj, _height, _porosity)

print("Radius of the water front for well", _well.get_name(), "=", radius)
}@
}
```

### Set crossflow at a target time

You can use a script to set the crossflow for wells at a target time.

```
START

DATE  "01-Oct-1997" 

CustomScript "Script" {
Script=@{
for well in Well('*'):
    currvalue = WellDef(well.get_name()).AllowCrossFlow.get()
    if WellDef(well.get_name()).AllowCrossFlow.get()!=True:
        WellDef(well.get_name()).AllowCrossFlow.set(True)
        print('changing well', well.get_name()+"'s AllowCrossFlow from",currvalue,'to',WellDef(well.get_name()).AllowCrossFlow.get())
    Well(well.get_name()).PiMultiplier.set(1.1)
}@
execute()
}
```

The `execute()` command executes the `CustomScript` node at a selected date. This is different from the `CustomControl` node that is executed unconditionally at the specified execution position, unless deactivated using the *Active* field, and the `CustomAction` node that is executed conditionally.

The main custom script sets the *AllowCrossFlow* field to true, prints a message to the PRT file after the successful execution of the script, and changes the *PiMultiplier* to 1.1.

After the execution of the custom script at the target time, the script writes a message to the PRT file as shown in [Figure 4](#python-extending-crossflowattargettime-fig1).

![](/assets/python_setcrossflow.c323929b8935a020dc4d1ae747fe6a5d9285f7b0be09ed694191ed12746b4312.367df99d.png)

### Calculate permeability thickness values for wells

You can use a script to calculate the permeability thickness (Kh) values of the grid cells in the regions around the wells at the end of each time step.

```
START

CustomControl "PermThick_report" {

ExecutionPosition=END_TIMESTEP

InitialScript=@{
import csv

_filename = Reservoir().get_name() + "PermThick.csv"
_file=open(_filename, 'w', newline='')

writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
wellnames = Well('*').get_name()

header = ['Well name', 'Report time', 'Permeability thichness']
writer.writerow(header)

[RegionMgr().create_regions_around_wells(region_list_name='REG'+i, wells=[Well(i)], cell_hops=1 ) for i in wellnames]
}@

Script=@{

for well in Well('*'):
    regname = "REG" + well.get_name()
	
    iperm=GridPropertyMgr().get_selection_property_values(regname, PERM_I).value
    jperm=GridPropertyMgr().get_selection_property_values(regname, PERM_J).value
    kperm=GridPropertyMgr().get_selection_property_values(regname, PERM_K).value
	
    perm = [pow(i*j*k, 1.0/3) for i,j,k in zip(iperm, jperm, kperm)]
	
    h = GridPropertyMgr().get_selection_property_values(regname,CELL_HEIGHT).value
	
    kHprod = [i*j for i,j in zip(perm, h)]
	
    writer.writerow([well.get_name(), FieldManagement().CurrentTime.get(), *kHprod])
}@

FinalScript=@{
_file.close()
}@
    
}
```

The name of the output file and regions around each well are defined and created in the initial script. Then, the main script gets the I, J, and K permeability and the height of the cells in the regions around the wells, then calculates the Kh values. The output file is closed in the final script.

After the execution of the custom script, the script writes the well names and Kh values to a CSV file as shown in [Figure 5](#python-extending-khvalues-fig1).

![](/assets/python_khvalues.a5c8eb1d2a33c905072e3420651d3affcf51f52c9092f6af87f47bac44092a06.367df99d.png)

### Get pressure and saturation for cells

You can use a script to get the pressure and water saturation values for each grid cell at a target time.

```
START

CustomControl "getPres_Sat" {
ExecutionPosition=END_TIMESTEP
InitialScript=@{
import csv
_filename = Reservoir().get_name() + "_Pres_Sat_Report.csv"
_file = open(_filename, 'w', newline='')
writer = csv.writer(_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
writer.writerow(['Report Time', 'CellID', 'Pressure', 'Water Saturation'])
}@

Script=@{
def report(rTime):
    _fCellId= StructuredInfo("CoarseGrid").FirstCellId.get()
    _nI = StructuredInfo("CoarseGrid").NumberCellsInI.get()
    _nJ = StructuredInfo("CoarseGrid").NumberCellsInJ.get()
    _nK = StructuredInfo("CoarseGrid").NumberCellsInK.get()	
    allData = []
    cellsSelected = list(range(_fCellId, (_nI*_nJ*_nK)+_fCellId))
    Data = []				
    CellSelectionFamily("Res").add_selection("resCells",cellsSelected)	
    cID = CellSelectionFamily("Res").Cells.value
    cPress = GridPropertyMgr().get_selection_property_values("Res",PRESSURE).value
    cSWAT = GridPropertyMgr().get_selection_property_values("Res",WATER_SATURATION).value	
    Data = [cID[0], cPress, cSWAT]
    allData = list(zip(*Data))	
    return allData
	
targetTimes = [1461, 2191]
for rTime in targetTimes:
    if (rTime == FieldManagement().CurrentTime.get()):
        nData = report(rTime)
        print("Writing requested report at time: ", rTime)
        for i in range(len(nData)):
            #writer.writerow([' ', nData[i][0], nData[i][1], nData[i][2]])
            writer.writerow([rTime, nData[i][0], nData[i][1], nData[i][2]])
}@
FinalScript=@{
_file.close()
}@
    
}
```

The name of the output file and the table header for the cell ID, pressure, and water saturation are defined under the initial script. In the main script, a `CellSelectionFamily` is created that includes every cell in the reservoir. Then, the `CellSelectionFamily` is used with `GridPropertyMgr().get_selection_property_values()` to get the corresponding pressure and water saturation of each cell under the family.

After the execution of the custom script, the script writes the cell IDs and pressure and water saturation values to a CSV file as shown in [Figure 6](#python-extending-getpressuresaturation-fig1).

![](/assets/python_getpressure.170ce1acbd1df5f536ce9a3c804d56aa8741b9bfa92d7f0337dead90bb0119c9.367df99d.png)

### Get pressure values for network nodes

You can use a script to get the pressure values from network nodes: getting all pressure values with one method call is recommended as it improves performance compared to calling a similar method for each network node.

```
CustomScript "Get_node_pressure" {
Script=@{

nodes_list = ["JUNC4", "SINK"]                           # size 2
props_list = [DoubleVectorProperty([0.0, 0.0],PRESSURE)] # size 1

pres = Network('NET1').NetworkNodeMgr().get_properties_for_nodes(nodes_list,props_list)

pres_junc4 = pres[0].value[0]
pres_sink = pres[0].value[1]

}@
execute()
}
```

`WellModelMgr().get_properties_for_wells()` works similarly with a list of `Well` objects instead of a list of `string` representing network nodes.

## Support information for custom scripting

Intersect supports the use of Python scripts to customize and extend built-in functionalities.

Commercial support is available for this feature. Such support is limited to ensuring that the Intersect scripting interfaces work correctly. Such support does not include application development, software programming support, or step-by-step instructions for software configuration beyond general usage questions. Advanced assistance might be provided under the terms of a specific Services agreement.

If an issue is found with the Python extensibility functionality, for example if an API is not working as expected, SLB will aim to fix these issues as quickly as possible in future releases of the software. To enable this, please provide the simplest possible script together with the simplest possible example reservoir model that in combination reproduce the problem. If assistance is required in the development of advanced Python scripting, please contact your local SLB representative.