ObjectNat 1.2.0__py3-none-any.whl → 1.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ObjectNat might be problematic. Click here for more details.

@@ -1,110 +1,121 @@
1
- from typing import Tuple
2
-
3
- import geopandas as gpd
4
- import numpy as np
5
- import pandas as pd
6
-
7
- from objectnat import config
8
-
9
- from .provision_model import Provision
10
-
11
- logger = config.logger
12
-
13
-
14
- def get_service_provision(
15
- buildings: gpd.GeoDataFrame,
16
- adjacency_matrix: pd.DataFrame,
17
- services: gpd.GeoDataFrame,
18
- threshold: int,
19
- buildings_demand_column: str = "demand",
20
- services_capacity_column: str = "capacity",
21
- ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
22
- """Calculate load from buildings with demands on the given services using the distances matrix between them.
23
-
24
- Args:
25
- services (gpd.GeoDataFrame): GeoDataFrame of services
26
- adjacency_matrix (pd.DataFrame): DataFrame representing the adjacency matrix
27
- buildings (gpd.GeoDataFrame): GeoDataFrame of demanded buildings
28
- threshold (int): Threshold value
29
- buildings_demand_column (str): column name of buildings demands
30
- services_capacity_column (str): column name of services capacity
31
- Returns:
32
- Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]: Tuple of GeoDataFrames representing provision
33
- buildings, provision services, and provision links
34
- """
35
- buildings = buildings.copy()
36
- services = services.copy()
37
- adjacency_matrix = adjacency_matrix.copy()
38
- buildings["demand"] = buildings[buildings_demand_column]
39
- services["capacity"] = services[services_capacity_column]
40
-
41
- provision_buildings, provision_services, provision_links = Provision(
42
- services=services,
43
- demanded_buildings=buildings,
44
- adjacency_matrix=adjacency_matrix,
45
- threshold=threshold,
46
- ).run()
47
- return provision_buildings, provision_services, provision_links
48
-
49
-
50
- def clip_provision(
51
- buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, selection_zone: gpd.GeoDataFrame
52
- ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
53
-
54
- assert selection_zone.crs == buildings.crs == services.crs == links.crs, (
55
- f"CRS mismatch: buildings_crs:{buildings.crs}, "
56
- f"links_crs:{links.crs} , "
57
- f"services_crs:{services.crs}, "
58
- f"selection_zone_crs:{selection_zone.crs}"
59
- )
60
- buildings = buildings.copy()
61
- links = links.copy()
62
- services = services.copy()
63
-
64
- s = buildings.intersects(selection_zone.union_all())
65
- buildings = buildings.loc[s[s].index]
66
- links = links[links["building_index"].isin(buildings.index.tolist())]
67
- services_to_keep = set(links["service_index"].tolist())
68
- services.drop(list(set(services.index.tolist()) - services_to_keep), inplace=True)
69
- return buildings, services, links
70
-
71
-
72
- def recalculate_links(
73
- buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, new_max_dist: float
74
- ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
75
- buildings = buildings.copy()
76
- services = services.copy()
77
- links = links.copy()
78
-
79
- links_to_recalculate = links[links["distance"] > new_max_dist]
80
- if len(links_to_recalculate) == 0:
81
- logger.warning("To clip distance exceeds max links distance, returning full provision")
82
- return buildings, services, links
83
-
84
- links_to_keep = links[links["distance"] <= new_max_dist]
85
- free_demand = links_to_recalculate.groupby("building_index").agg({"demand": list, "distance": list})
86
- free_demand["distance"] = free_demand.apply(
87
- lambda x: sum((x1 * x2) for x1, x2 in zip(x.demand, x.distance)), axis=1
88
- )
89
- free_demand["demand"] = free_demand["demand"].apply(sum)
90
- free_demand = free_demand.reindex(buildings.index, fill_value=0)
91
- new_sum_time = (buildings["supplied_demands_within"] + buildings["supplied_demands_without"]) * buildings[
92
- "avg_dist"
93
- ] - free_demand["distance"]
94
-
95
- buildings["demand_left"] = buildings["demand_left"] + free_demand["demand"]
96
- buildings["supplied_demands_without"] = buildings["supplied_demands_without"] - free_demand["demand"]
97
- buildings["avg_dist"] = new_sum_time / (
98
- buildings["supplied_demands_without"] + buildings["supplied_demands_within"]
99
- )
100
- buildings["avg_dist"] = buildings.apply(
101
- lambda x: np.nan if (x["demand"] == x["demand_left"]) else round(x["avg_dist"], 2), axis=1
102
- )
103
-
104
- free_capacity = links_to_recalculate.groupby("service_index").agg({"demand": "sum"})
105
- free_capacity = free_capacity.reindex(services.index, fill_value=0)
106
- services["capacity_left"] = services["capacity_left"] + free_capacity["demand"]
107
- services["carried_capacity_without"] = services["carried_capacity_without"] - free_capacity["demand"]
108
- services["service_load"] = services["service_load"] - free_capacity["demand"]
109
-
110
- return buildings, services, links_to_keep
1
+ from typing import Tuple
2
+
3
+ import geopandas as gpd
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from objectnat import config
8
+
9
+ from .provision_model import Provision
10
+
11
+ logger = config.logger
12
+
13
+
14
+ def get_service_provision(
15
+ buildings: gpd.GeoDataFrame,
16
+ adjacency_matrix: pd.DataFrame,
17
+ services: gpd.GeoDataFrame,
18
+ threshold: int,
19
+ buildings_demand_column: str = "demand",
20
+ services_capacity_column: str = "capacity",
21
+ pandarallel_init_kwargs: dict = None,
22
+ ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
23
+ """Calculate load from buildings with demands on the given services using the distances matrix between them.
24
+
25
+ Parameters:
26
+ services (gpd.GeoDataFrame):
27
+ GeoDataFrame of services
28
+ adjacency_matrix (pd.DataFrame):
29
+ DataFrame representing the adjacency matrix
30
+ buildings (gpd.GeoDataFrame):
31
+ GeoDataFrame of demanded buildings
32
+ threshold (int):
33
+ Threshold value
34
+ buildings_demand_column (str):
35
+ column name of buildings demands
36
+ services_capacity_column (str):
37
+ column name of services capacity
38
+ pandarallel_init_kwargs (dict):
39
+ Dictionary of keyword arguments to pass to pandarallel
40
+
41
+ Returns:
42
+ (Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]): Tuple of GeoDataFrames representing provision
43
+ buildings, provision services, and provision links
44
+ """
45
+ buildings = buildings.copy()
46
+ services = services.copy()
47
+ adjacency_matrix = adjacency_matrix.copy()
48
+ buildings["demand"] = buildings[buildings_demand_column]
49
+ services["capacity"] = services[services_capacity_column]
50
+
51
+ provision_buildings, provision_services, provision_links = Provision(
52
+ services=services,
53
+ demanded_buildings=buildings,
54
+ adjacency_matrix=adjacency_matrix,
55
+ threshold=threshold,
56
+ pandarallel_init_kwargs=pandarallel_init_kwargs,
57
+ ).run()
58
+ return provision_buildings, provision_services, provision_links
59
+
60
+
61
+ def clip_provision(
62
+ buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, selection_zone: gpd.GeoDataFrame
63
+ ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
64
+
65
+ assert selection_zone.crs == buildings.crs == services.crs == links.crs, (
66
+ f"CRS mismatch: buildings_crs:{buildings.crs}, "
67
+ f"links_crs:{links.crs} , "
68
+ f"services_crs:{services.crs}, "
69
+ f"selection_zone_crs:{selection_zone.crs}"
70
+ )
71
+ buildings = buildings.copy()
72
+ links = links.copy()
73
+ services = services.copy()
74
+
75
+ s = buildings.intersects(selection_zone.union_all())
76
+ buildings = buildings.loc[s[s].index]
77
+ links = links[links["building_index"].isin(buildings.index.tolist())]
78
+ services_to_keep = set(links["service_index"].tolist())
79
+ services.drop(list(set(services.index.tolist()) - services_to_keep), inplace=True)
80
+ return buildings, services, links
81
+
82
+
83
+ def recalculate_links(
84
+ buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, new_max_dist: float
85
+ ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
86
+ buildings = buildings.copy()
87
+ services = services.copy()
88
+ links = links.copy()
89
+
90
+ links_to_recalculate = links[links["distance"] > new_max_dist]
91
+ if len(links_to_recalculate) == 0:
92
+ logger.warning("To clip distance exceeds max links distance, returning full provision")
93
+ return buildings, services, links
94
+
95
+ links_to_keep = links[links["distance"] <= new_max_dist]
96
+ free_demand = links_to_recalculate.groupby("building_index").agg({"demand": list, "distance": list})
97
+ free_demand["distance"] = free_demand.apply(
98
+ lambda x: sum((x1 * x2) for x1, x2 in zip(x.demand, x.distance)), axis=1
99
+ )
100
+ free_demand["demand"] = free_demand["demand"].apply(sum)
101
+ free_demand = free_demand.reindex(buildings.index, fill_value=0)
102
+ new_sum_time = (buildings["supplied_demands_within"] + buildings["supplied_demands_without"]) * buildings[
103
+ "avg_dist"
104
+ ] - free_demand["distance"]
105
+
106
+ buildings["demand_left"] = buildings["demand_left"] + free_demand["demand"]
107
+ buildings["supplied_demands_without"] = buildings["supplied_demands_without"] - free_demand["demand"]
108
+ buildings["avg_dist"] = new_sum_time / (
109
+ buildings["supplied_demands_without"] + buildings["supplied_demands_within"]
110
+ )
111
+ buildings["avg_dist"] = buildings.apply(
112
+ lambda x: np.nan if (x["demand"] == x["demand_left"]) else round(x["avg_dist"], 2), axis=1
113
+ )
114
+
115
+ free_capacity = links_to_recalculate.groupby("service_index").agg({"demand": "sum"})
116
+ free_capacity = free_capacity.reindex(services.index, fill_value=0)
117
+ services["capacity_left"] = services["capacity_left"] + free_capacity["demand"]
118
+ services["carried_capacity_without"] = services["carried_capacity_without"] - free_capacity["demand"]
119
+ services["service_load"] = services["service_load"] - free_capacity["demand"]
120
+
121
+ return buildings, services, links_to_keep
@@ -40,6 +40,7 @@ class Provision:
40
40
  demanded_buildings: gpd.GeoDataFrame,
41
41
  adjacency_matrix: pd.DataFrame,
42
42
  threshold: int,
43
+ pandarallel_init_kwargs: dict = None,
43
44
  ):
44
45
  self.services = self.ensure_services(services.copy())
45
46
  self.demanded_buildings = self.ensure_buildings(demanded_buildings.copy())
@@ -48,7 +49,16 @@ class Provision:
48
49
  ).copy()
49
50
  self.threshold = threshold
50
51
  self.services.to_crs(self.demanded_buildings.crs, inplace=True)
51
- pandarallel.initialize(progress_bar=False, verbose=0, use_memory_fs=config.pandarallel_use_file_system)
52
+
53
+ if pandarallel_init_kwargs is None:
54
+ pandarallel_init_kwargs = {}
55
+
56
+ pandarallel_init_kwargs["use_memory_fs"] = pandarallel_init_kwargs.get(
57
+ "use_memory_fs", config.pandarallel_use_file_system
58
+ )
59
+ pandarallel_init_kwargs["progress_bar"] = pandarallel_init_kwargs.get("progress_bar", False)
60
+ pandarallel_init_kwargs["verbose"] = pandarallel_init_kwargs.get("verbose", 0)
61
+ pandarallel.initialize(**pandarallel_init_kwargs)
52
62
 
53
63
  @staticmethod
54
64
  def ensure_buildings(v: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
@@ -91,6 +101,7 @@ class Provision:
91
101
  import pandas as pd # pylint: disable=redefined-outer-name,reimported,import-outside-toplevel
92
102
 
93
103
  c = services_table.loc[loc.name]["capacity_left"]
104
+ logger.debug(f"Capacity left: {c}")
94
105
  p = 1 / loc / loc
95
106
  p = p / p.sum()
96
107
  threshold = p.quantile(best_houses)