Working With Boundary Data in the bc_dbase.csv#
The below examples show how to view the bc_dbase.csv file, extract boundary time-series data from it, and how to make edits.
The following example uses models provided in the TUFLOW Example Model Dataset.
Extracting Time-Series Boundaries - Simple Example#
The first example shows how to read boundary data from the bc_dbase.csv file from a model that
does not use an event file and does not require any event arguments to run. The entry
point to the model will be via the TCF
class, which is used to load the TUFLOW model.
The model used in this example is EG00_001.tcf
from the TUFLOW example models.
>>> from pytuflow import TCF
>>> tcf = TCF('path/to/EG00_001.tcf')
We can get the bc_dbase instance from the TCF using the TCF.bc_dbase()
method.
>>> bc_dbase = tcf.bc_dbase()
The database content is stored in a pandas DataFrame, and we can view this by accessing the
BCDatabase.df
attribute:
>>> print(bc_dbase.df)
Source Column 1 Column 2 Add Col 1 Mult Col 2 Add Col 2 Column 3 Column 4
Name
FC01 EG00_001.csv inflow_time_hr inflow_FC01 NaN NaN NaN NaN NaN
This model contains a single inflow (QT
) boundary called "FC01"
. To view the time-series data for this
boundary, we can use the BCDatabase.value()
method, which returns a
pandas DataFrame with the time-series data for the boundary:
>>> fc01 = bc_dbase.value('FC01')
>>> print(fc01)
inflow_time_hr inflow_FC01
0 0.000 0.00
1 0.083 0.84
2 0.167 3.31
3 0.250 4.60
4 0.333 7.03
5 0.417 12.39
... ... ...
38 3.167 1.77
39 3.250 1.60
40 3.333 1.45
Note, the boundary name that is passed into the BCDatabase.value()
method
is not case-sensitive, so "FC01"
, "fc01"
, and "Fc01"
will all return the same data.
Extracting Time-Series Boundaries - With Events#
This example shows how to extract boundary data from a model that is using an event file to manage multiple events.
This is a much more common use case, as many real world models will use an event file. This example
uses EG16_~e1~_~e2~_005.tcf
from the TUFLOW example models, which has multiple events defined in the event file.
First, let’s load the TCF file, then get the bc_dbase instance and print the DataFrame to see what boundaries are defined in the model:
>>> from pytuflow import TCF
>>> tcf = TCF('path/to/EG16_~e1~_~e2~_005.tcf')
>>> bc_dbase = tcf.bc_dbase()
>>> print(bc_dbase.df)
Source Column 1 Column 2 Add Col 1 Mult Col 2 Add Col 2 Column 3 Column 4
Name
FC01 EG16__event1__event2_.csv inflow_time_hr inflow_FC01 NaN NaN NaN NaN NaN
This database is very similar to the previous, simple example, but it is using _event1_
and _event2_
in the
source file name, which are variables that will be replaced with the event names when the model is run.
What happens if we try and get the time-series data for "FC01"
?
>>> fc01 = bc_dbase.value('FC01')
Traceback (most recent call last):
...
ValueError: Database requires a context to resolve value.
Python raises an exception and we end up with the message “Database requires a context to resolve value.” So the error message is telling us that it was unable to resolve the value because it needs a run context to do so.
If you need to, you can check the available events in the model by using the TCF.event_database()
method:
>>> event_db = tcf.event_database()
>>> print(event_db)
{'Q100': {'_event1_': '100yr'},
'QPMF': {'_event1_': 'PMFyr'},
'2hr': {'_event2_': '2hr'},
'4hr': {'_event2_': '4hr'}}
The event database is effectively just a dictionary. The keys are the event names, and the values are the event variables and their corresponding values.
So to get the time-series data for "FC01"
, we can use the TCF.context()
method and pass in event arguments so that the database can resolve the values:
>>> bc_dbase = tcf.context('-e1 Q100 -e2 2hr').bc_dbase()
>>> fc01 = bc_dbase.value('FC01')
>>> print(fc01)
inflow_time_hr inflow_FC01
0 0.000 0.00
1 0.083 0.84
2 0.167 3.31
3 0.250 4.60
4 0.333 7.03
5 0.417 12.39
... ... ...
38 3.167 1.77
39 3.250 1.60
40 3.333 1.45
Another way to do the same thing is to use the BCDatabase.context()
method.
Consider the following example using EG16_~s1~_~s2~_~e1~_~e2~_006.tcf
which also includes scenarios:
>>> tcf = TCF('path/to/EG16_~s1~_~s2~_~e1~_~e2~_006.tcf')
>>> bc_dbase = tcf.context('-e1 Q100 -e2 2hr').bc_dbase()
Traceback (most recent call last):
...
ValueError: Pause command encountered: Not Valid Cell Size - See TCF
We now get a new error message, which is telling us that the model is hitting a pause command. This is because we
aren’t passing any scenario arguments to the run context. So, in this case, it might be easier just to pass
in the event arguments to the BCDatabase.context()
method without worrying
about scenarios, since in this case the scenarios are not relevant to the boundary data:
>>> fc01 = tcf.bc_dbase().context('-e1 Q100 -e2 2hr').value('FC01')
>>> print(fc01)
inflow_time_hr inflow_FC01
0 0.000 0.00
1 0.083 0.84
2 0.167 3.31
3 0.250 4.60
4 0.333 7.03
5 0.417 12.39
... ... ...
38 3.167 1.77
39 3.250 1.60
40 3.333 1.45
Using the BCDatabase.context()
will also be quicker than using the
TCF.context()
method, as it will only resolve the bc_dbase. Calling it from
the TCF class, the context will be passed on to all it’s children (other control files and databases). Although it’s
not necessarily slow, it will be slower than resolving only the boundary database.
Editing the Boundary Database#
Editing the boundary database is done by modifying the pandas DataFrame that is stored in the
BCDatabase.df
attribute. For example, we can add a new boundary that might be
a constant downstream water level boundary with the name "dns_bndry"
and a has value of 0.5m
. We will add this
to the previous model we were working with, EG16_~e1~_~e2~_005.tcf
:
>>> import pandas as pd
>>> tcf = TCF('path/to/EG16_~e1~_~e2~_005.tcf')
>>> bc_dbase = tcf.bc_dbase()
>>> val_col_name = bc_dbase.df.columns[2]
>>> dns_bndry = pd.DataFrame({val_col_name: [0.5]}, index=['dns_bndry'])
>>> bc_dbase.df = pd.concat([bc_dbase.df, dns_bndry], axis=0)
>>> print(bc_dbase.df)
Source Column 1 Column 2 Add Col 1 Mult Col 2 Add Col 2 Column 3 Column 4
FC01 EG16__event1__event2_.csv inflow_time_hr inflow_FC01 NaN NaN NaN NaN NaN
dns_bndry NaN NaN 0.5 NaN NaN NaN NaN NaN
In the above example, we first get the name of the value column from the DataFrame to ensure we can merge our new boundary data correctly. The “Name” column is the index, so the value column (“Column 2” in this case) is at index 2. We then create a new DataFrame with the new boundary data, and concatenate it with the existing DataFrame.
Modifying the bc_dbase DataFrame will set the BCDatabase
instance as dirty
, which means when we call the
TCF.write()
method, it will write the modified BCDatabase to disk.
We can test this by writing the modified files to disk using the TCF.write()
method. We will
save the updated model as 005a
:
>>> tcf.write(inc='005a')
<TuflowControlFile> EG16_~e1~_~e2~_005a.tcf
We can check the new file to confirm that the bc_dbase_EG15_005.csv
file name has been incremented to bc_dbase_EG15_005a.csv
:
>>> tcf.find_input('BC Database')[0]
<DatabaseInput> BC Database == ..\bc_dbase\bc_dbase_EG16_005a.csv
>>> tcf.bc_dbase().fpath
WindowsPath('../bc_dbase/bc_dbase_EG16_005a.csv')
>>> tcf.bc_dbase().fpath.exists()
True