Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement object based "add" API in Python #760

Closed
SnippenE opened this issue Nov 9, 2023 · 7 comments · Fixed by #1110
Closed

Implement object based "add" API in Python #760

SnippenE opened this issue Nov 9, 2023 · 7 comments · Fixed by #1110
Assignees
Labels
python Relates to one of the Ribasim python packages

Comments

@SnippenE
Copy link

SnippenE commented Nov 9, 2023

Will be longer than sprint 10, probably done in 12.

@evetion evetion added this to Ribasim Nov 9, 2023
@SnippenE SnippenE converted this from a draft issue Nov 9, 2023
@evetion evetion self-assigned this Nov 16, 2023
@evetion evetion moved this from Sprint backlog to 🏗 In progress in Ribasim Nov 20, 2023
@visr visr added the python Relates to one of the Ribasim python packages label Nov 23, 2023
@SnippenE SnippenE moved this from 🏗 In progress to What's next in Ribasim Nov 23, 2023
visr pushed a commit that referenced this issue Nov 29, 2023
Fixes #811 

More groundwork for #760, as doing `model.node.add()`, requires
accessing the `node._parent.network.`
@evetion evetion moved this from What's next to Sprint backlog in Ribasim Jan 9, 2024
@evetion
Copy link
Member

evetion commented Jan 9, 2024

Let's wait for #912 to be merged first.

@Jingru923 Jingru923 moved this from Sprint backlog to What's next in Ribasim Feb 1, 2024
@Hofer-Julian
Copy link
Contributor

See #588 (comment)

@Hofer-Julian
Copy link
Contributor

WIP by @visr and me:

basin = ribasim.Basin([
    {
        "profile": pd.DataFrame(data={"area": [1.0,3.0], "level": [1.1, 2.2]}),
        "static": pd.DataFrame(data={"precipitation": [1.0,3.0], "control_state": [1.1, 2.2]}),
        # use dictionaries [str:sequence],
        # later consider accepting pd.DataFrame or dicts with numpy arrays
        "static": {"precipitation": [1.0,3.0], "control_state": [1.1, 2.2]},
        # later accept more timestamp types
        # only support either static or time per node_id
        # "time": {"time": [pd.Timestamp(2020), pd.Timestamp(2021)], "precipitation": [1.0,3.0]},
        "node": {
            # allow tuples, call gpd.points_from_xy ourselves
            "geometry": (2.0,3.6),
            "name": "IJsselmeer",
            "allocation_network_id": 5,
        }
    }
])

# autoincrement fid at object creation
# generate on demand
model.network.node.df
model.basins.geometry.df

class Basin:
    static: DataFrame
    time: DataFrame
    profile: DataFrame
    node: DataFrame

# add a node
basin.add({})
# and connect it
edge.add(basin[-1], terminal[-1])

terminal
# show
terminal.plot()

#

model.basin

basins[0]
basins[node_id]
iterate(basins)

# connect
# connect(basins[2], pumps[1])

ribasim.Edges([
    {
        "type": "flow",
        "from": basins[0],
        "to": pumps[0],
        "geometry": ...,
        "name": ...,
        "allocation_network_id": ...,
     }
])

@Hofer-Julian
Copy link
Contributor

And the newest iteration, assuming we now require ids to only be unique per node_type:

basin = ribasim.Basin([
    {
        "profile": pd.DataFrame(data={"area": [1.0,3.0], "level": [1.1, 2.2]}),
        "static": pd.DataFrame(data={"precipitation": [1.0,3.0], "control_state": [1.1, 2.2]}),
        # use dictionaries [str:sequence],
        # later consider accepting pd.DataFrame or dicts with numpy arrays
        "static": {"precipitation": [1.0,3.0], "control_state": [1.1, 2.2]},
        # later accept more timestamp types
        # only support either static or time per node_id
        # "time": {"time": [pd.Timestamp(2020), pd.Timestamp(2021)], "precipitation": [1.0,3.0]},
        "node": {
            # allow tuples, call gpd.points_from_xy ourselves
            "geometry": (2.0,3.6),
            "name": "IJsselmeer",
            "allocation_network_id": 5,
        }
    }
])

# autoincrement fid at object creation
# generate on demand
model.network.node.df
model.basins.geometry.df

class Basin:
    static: DataFrame
    time: DataFrame
    profile: DataFrame
    node: DataFrame

# add a node
basin.add({})
# and connect it
edge.add(basin[-1], terminal[-1])

terminal
# show
terminal.plot()

#

model.basin

basins[0]
basins[node_id]
iterate(basins)

# connect
# connect(basins[2], pumps[1])

ribasim.Edges([
    {
        "type": "flow",
        "from": basins[0],
        "to": pumps[0],
        "geometry": ...,
        "name": ...,
        "allocation_network_id": ...,
     }
])

@visr
Copy link
Member

visr commented Feb 8, 2024

Latest version:

import ribasim

model = ribasim.Model(starttime=starttime, endtime=endtime)

model.basin = ribasim.Basin(
    [
        {
            # use dictionaries [str:sequence],
            "profile": {"area": [1.0, 3.0], "level": [1.1, 2.2]},
            "static": {"precipitation": [1.0, 3.0], "control_state": [1.1, 2.2]},
            # later consider accepting pd.DataFrame or dicts with numpy arrays
            # "static": pd.DataFrame(data={"precipitation": [1.0,3.0], "control_state": [1.1, 2.2]}),
            # later accept more timestamp types
            # only support either static or time per node_id
            # "time": {"time": [pd.Timestamp(2020), pd.Timestamp(2021)], "precipitation": [1.0,3.0]},
            "node": {
                "id": 2,
                # allow tuples, call gpd.points_from_xy ourselves
                "geometry": (2.0, 3.6),
                "name": "IJsselmeer",
                "allocation_network_id": 5,
            },
        }
    ]
)

model.basin.add({...})
model.basins.replace(basin_id)
model.remove(ribasim.Basin, 2)  # can remove edges
model.replace(ribasim.Basin, 2, ribasim.Pump([{...}]))  # can remove edges

# the node table becomes a virtual concat of the node tables of all node types
# that is constructed on demand


class Basin:
    static: DataFrame
    time: DataFrame
    profile: DataFrame
    node: DataFrame


class Model:
    basin: Basin
    pump: Pump


# connect it
# TODO edge ID stability?
# TODO use (ribasim.Basin, 10) or ribasim.BasinID(10) or something else?
model.edge.add(
    {
        "id": 10,
        "type": "flow",
        "from": (ribasim.Basin, 10),
        "to": (ribasim.Terminal, 20),
    }
)
model.edge.remove(ribasim.Basin, 10, ribasim.Terminal, 20)

terminal
# show
terminal.plot()

#

model.basin

basin[3]  # basin ID #3

basin.plot()
pump.plot()

ribasim.Edges(
    [
        {
            "type": "flow",
            "from": basins[0],
            "to": pumps[0],
            "geometry": ...,
            "name": ...,
            "allocation_network_id": ...,
        }
    ]
)


## Remove a pump (and its associated edges?)
## Replace a pump with a pump, keeping edges (and node_id?) intact
## Replace a pump with an outlet, keeping edges (and node_id?) intact
## Add a pump and connect to an ID

## Can we drop global ID altogether in favor of a node specific ID? Probably.

@Hofer-Julian Hofer-Julian moved this from What's next to 🏗 In progress in Ribasim Feb 9, 2024
@Hofer-Julian Hofer-Julian assigned Hofer-Julian and unassigned evetion Feb 9, 2024
@Hofer-Julian
Copy link
Contributor

Hofer-Julian commented Feb 12, 2024

Latest version:

import ribasim

model = ribasim.Model(starttime=starttime, endtime=endtime)

model.basin.add(
    ribasim.Basin(
        # use dictionaries [str:sequence],
        profile=Dataframe({"area": [1.0, 3.0], "level": [1.1, 2.2]}),
        static=DataFrame{"precipitation": [1.0, 3.0], "control_state": [1.1, 2.2]}),
        # later accept more timestamp types
        # only support either static or time per node_id
        # "time": DataFrame({"time": [pd.Timestamp(2020), pd.Timestamp(2021)], "precipitation": [1.0,3.0]}),
        node={
            "id": 2,
            # allow tuples, call gpd.points_from_xy ourselves
            "geometry": (2.0, 3.6),
            "name": "IJsselmeer",
            "allocation_network_id": 5,
        }
))
model.basins.replace(basin_id)
model.remove(ribasim.Basin, 2)  # can remove edges
model.replace(ribasim.Basin, 2, ribasim.Pump([{...}]))  # can remove edges

# the node table becomes a virtual concat of the node tables of all node types
# that is constructed on demand


# connect it
# TODO edge ID stability?
# TODO use (ribasim.Basin, 10) or ribasim.BasinID(10) or something else?
model.edges.add(
    edge_id=10,
    edge_type="flow",
    from_node_type=ribasim.Basin,
    from_node_id=10,
    to_node_type=ribasim.Terminal,
    to_node_id=20
)
model.edge.remove(ribasim.Basin, 10, ribasim.Terminal, 20)


## Remove a pump (and its associated edges?)
## Replace a pump with a pump, keeping edges (and node_id?) intact
## Replace a pump with an outlet, keeping edges (and node_id?) intact
## Add a pump and connect to an ID

## Can we drop global ID altogether in favor of a node specific ID? Probably.

@Hofer-Julian
Copy link
Contributor

Hofer-Julian commented Feb 29, 2024

import ribasim

model = ribasim.Model(starttime=starttime, endtime=endtime)
model.basins.add(Node(node_id=1, geometry=point), 
                 [basin.Profile(area=[0.01, 1000.0], level=[0.0, 1.0]), 
                  basin.State(level=[1.0])])

model.basins.replace(basin_id)
model.remove(ribasim.Basin, 2)  # can remove edges
model.replace(ribasim.Basin, 2, ribasim.Pump([{...}]))  # can remove edges

# the node table becomes a virtual concat of the node tables of all node types
# that is constructed on demand


# connect it
# TODO edge ID stability?
# TODO use (ribasim.Basin, 10) or ribasim.BasinID(10) or something else?
model.edges.add(
    from_node=model.basins[1],
    to_node=model.manning_resistances[2],
    edge_type="flow",
)
model.edge.remove(model.basins[1], model.manning_resistances[2])


## Remove a pump (and its associated edges?)
## Replace a pump with a pump, keeping edges (and node_id?) intact
## Replace a pump with an outlet, keeping edges (and node_id?) intact
## Add a pump and connect to an ID

## Can we drop global ID altogether in favor of a node specific ID? Probably.

visr added a commit that referenced this issue Mar 12, 2024
Fixes #760
Fixes #252

Follow ups:
- More validation
- Avoid blocking of database by qgis
- Plotting of control edges
- Reading of model

---------

Co-authored-by: Martijn Visser <[email protected]>
Co-authored-by: Hofer-Julian <[email protected]>
@github-project-automation github-project-automation bot moved this from 🏗 In progress to ✅ Done in Ribasim Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
python Relates to one of the Ribasim python packages
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants