engforge 0.3.0__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.
Files changed (69) hide show
  1. engforge/__init__.py +26 -0
  2. engforge/_testing_components.py +864 -0
  3. engforge/analysis.py +257 -0
  4. engforge/attr_dynamics.py +222 -0
  5. engforge/attr_plotting.py +811 -0
  6. engforge/attr_signals.py +116 -0
  7. engforge/attr_slots.py +211 -0
  8. engforge/attr_solver.py +443 -0
  9. engforge/attributes.py +326 -0
  10. engforge/common.py +76 -0
  11. engforge/component_collections.py +276 -0
  12. engforge/components.py +55 -0
  13. engforge/configuration.py +673 -0
  14. engforge/dataframe.py +185 -0
  15. engforge/datastores/__init__.py +116 -0
  16. engforge/datastores/data.py +519 -0
  17. engforge/datastores/gdocs.py +2650 -0
  18. engforge/datastores/reporting.py +935 -0
  19. engforge/datastores/secrets.py +549 -0
  20. engforge/dynamics.py +662 -0
  21. engforge/eng/__init__.py +0 -0
  22. engforge/eng/costs.py +1459 -0
  23. engforge/eng/fluid_material.py +279 -0
  24. engforge/eng/geometry.py +1060 -0
  25. engforge/eng/pipes.py +1211 -0
  26. engforge/eng/prediction.py +366 -0
  27. engforge/eng/solid_materials.py +445 -0
  28. engforge/eng/structure.py +2228 -0
  29. engforge/eng/structure_beams.py +821 -0
  30. engforge/eng/thermodynamics.py +271 -0
  31. engforge/engforge_attributes.py +380 -0
  32. engforge/env_var.py +214 -0
  33. engforge/locations.py +18 -0
  34. engforge/logging.py +290 -0
  35. engforge/patterns.py +449 -0
  36. engforge/problem_context.py +2251 -0
  37. engforge/properties.py +501 -0
  38. engforge/reporting.py +595 -0
  39. engforge/solveable.py +964 -0
  40. engforge/solver.py +316 -0
  41. engforge/solver_utils.py +726 -0
  42. engforge/system.py +143 -0
  43. engforge/system_reference.py +299 -0
  44. engforge/tabulation.py +288 -0
  45. engforge/test/__init__.py +2 -0
  46. engforge/test/_pre_test_structures.py +221 -0
  47. engforge/test/report_testing.py +158 -0
  48. engforge/test/test_airfilter.py +91 -0
  49. engforge/test/test_analysis.py +3 -0
  50. engforge/test/test_comp_iter.py +196 -0
  51. engforge/test/test_composition.py +139 -0
  52. engforge/test/test_costs.py +338 -0
  53. engforge/test/test_dynamics.py +109 -0
  54. engforge/test/test_dynamics_spaces.py +39 -0
  55. engforge/test/test_four_bar.py +97 -0
  56. engforge/test/test_modules.py +72 -0
  57. engforge/test/test_performance.py +93 -0
  58. engforge/test/test_pipes.py +49 -0
  59. engforge/test/test_problem.py +411 -0
  60. engforge/test/test_problem_deepscoping.py +68 -0
  61. engforge/test/test_slider_crank.py +51 -0
  62. engforge/test/test_solver.py +343 -0
  63. engforge/test/test_tabulation.py +233 -0
  64. engforge/typing.py +92 -0
  65. engforge-0.3.0.dist-info/METADATA +392 -0
  66. engforge-0.3.0.dist-info/RECORD +69 -0
  67. engforge-0.3.0.dist-info/WHEEL +5 -0
  68. engforge-0.3.0.dist-info/licenses/LICENSE +7 -0
  69. engforge-0.3.0.dist-info/top_level.txt +1 -0
engforge/dataframe.py ADDED
@@ -0,0 +1,185 @@
1
+ """Dataframe Module:
2
+
3
+ Store data in dataframes and provide a simple interface to manipulate it.
4
+ """
5
+
6
+ from contextlib import contextmanager
7
+ import attr
8
+
9
+ from engforge.common import inst_vectorize, chunks
10
+ from engforge.properties import engforge_prop
11
+
12
+ # from engforge.configuration import Configuration, forge
13
+ from engforge.logging import LoggingMixin
14
+ from engforge.typing import *
15
+ from engforge.properties import *
16
+ from typing import Callable
17
+
18
+ import numpy
19
+ import pandas
20
+ import os
21
+ import collections
22
+ import uuid
23
+
24
+
25
+ class DataFrameLog(LoggingMixin):
26
+ pass
27
+
28
+
29
+ log = DataFrameLog()
30
+
31
+
32
+ # Dataframe interrogation functions
33
+ def is_uniform(s: pandas.Series):
34
+ a = s.to_numpy() # s.values (pandas<0.24)
35
+
36
+ if numpy.all(a == a[0]):
37
+ return True
38
+ try:
39
+ #
40
+ if not numpy.isfinite(a).any():
41
+ return True
42
+ except:
43
+ pass
44
+ return False
45
+
46
+
47
+ # key_func = lambda kv: len(kv[0].split('.'))*len(kv[1])
48
+ # length of matches / length of key
49
+ key_func = lambda kv: len(kv[1]) / len(kv[0].split("."))
50
+ # key_func = lambda kv: len(kv[1])
51
+
52
+
53
+ # TODO: remove duplicate columns
54
+ # mtches = collections.defaultdict(set)
55
+ # dfv = ecs.dataframe_variants[0]
56
+ # for v1,v2 in itertools.combinations(dfv.columns,2):
57
+ # if numpy.all(dfv[v1]==dfv[v2]):
58
+ #
59
+ # mtches[v1].add(v2)
60
+ # mtches[v2].add(v1)
61
+
62
+
63
+ # TODO: integrate statistical output of dataframe, if at all in problem domain
64
+ # 1. stats_mode: mean, median,min,max mode, std, var, skew, kurtosis
65
+ # 2. min_mode: mean,median,std,min,max
66
+ # 3. sub_mode: store the dataframe completely separately
67
+ class dataframe_property(engforge_prop):
68
+ pass
69
+
70
+
71
+ # aliases
72
+ dataframe_prop = dataframe_property
73
+ df_prop = dataframe_property
74
+
75
+
76
+ def determine_split(raw, top: int = 1, key_f=key_func):
77
+ parents = {}
78
+
79
+ for rw in raw:
80
+ grp = rw.split(".")
81
+ for i in range(len(grp)):
82
+ tkn = ".".join(grp[0 : i + 1])
83
+ parents[tkn] = set()
84
+
85
+ for rw in raw:
86
+ for par in parents:
87
+ if rw.startswith(par):
88
+ parents[par].add(rw)
89
+
90
+ grps = sorted(parents.items(), key=key_f, reverse=True)[:top]
91
+ return [g[0] for g in grps]
92
+
93
+
94
+ def split_dataframe(df: pandas.DataFrame) -> tuple:
95
+ """split dataframe into a dictionary of invariants and a dataframe of variable values
96
+
97
+ :returns tuple: constants,dataframe
98
+ """
99
+ uniform = {}
100
+ for s in df:
101
+ c = df[s]
102
+ if is_uniform(c):
103
+ uniform[s] = c.iloc[0]
104
+
105
+ df_unique = df.copy().drop(columns=list(uniform))
106
+ return uniform, df_unique if len(df_unique) > 0 else df_unique
107
+
108
+
109
+ class DataframeMixin:
110
+ dataframe: pandas.DataFrame
111
+
112
+ _split_dataframe_func = staticmethod(split_dataframe)
113
+ _determine_split_func = staticmethod(determine_split)
114
+
115
+ def smart_split_dataframe(self, df=None, split_groups=0, key_f=key_func):
116
+ """splits dataframe between constant values and variants"""
117
+ if df is None:
118
+ df = self.dataframe
119
+ out = {}
120
+ const, vardf = split_dataframe(df)
121
+ out["constants"] = const
122
+ columns = set(vardf.columns)
123
+ split_groups = min(split_groups, len(columns) - 1)
124
+ if split_groups == 0:
125
+ out["variants"] = vardf
126
+ else:
127
+ nconst = {}
128
+ cgrp = determine_split(const, min(split_groups, len(const) - 1))
129
+ for i, grp in enumerate(sorted(cgrp, reverse=True)):
130
+ columns = set(const)
131
+ bad_columns = [c for c in columns if not c.startswith(grp)]
132
+ good_columns = [c for c in columns if c.startswith(grp)]
133
+ nconst[grp] = {c: const[c] for c in good_columns}
134
+ for c in good_columns:
135
+ if c in columns:
136
+ columns.remove(c)
137
+ out["constants"] = nconst
138
+
139
+ raw = sorted(set(df.columns))
140
+ grps = determine_split(raw, split_groups, key_f=key_f)
141
+
142
+ for i, grp in enumerate(sorted(grps, reverse=True)):
143
+ columns = set(vardf.columns)
144
+ bad_columns = [c for c in columns if not c.startswith(grp)]
145
+ good_columns = [c for c in columns if c.startswith(grp)]
146
+ out[grp] = vardf.copy().drop(columns=bad_columns)
147
+ # remove columns from vardf
148
+ vardf = vardf.drop(columns=good_columns)
149
+ if vardf.size > 0:
150
+ out["misc"] = vardf
151
+ return out
152
+
153
+ @solver_cached
154
+ def _split_dataframe(self):
155
+ """splits dataframe between constant values and variants"""
156
+ return split_dataframe(self.dataframe)
157
+
158
+ @property
159
+ def dataframe_constants(self):
160
+ return self._split_dataframe[0]
161
+
162
+ @property
163
+ def dataframe_variants(self):
164
+ o = self._split_dataframe[1:]
165
+ if isinstance(o, (list, tuple)):
166
+ if len(o) == 1:
167
+ return o[0]
168
+ return o
169
+
170
+ def format_columns(self, dataframe: pandas.DataFrame):
171
+ # replace(".", "_")
172
+ dataframe.rename(lambda x: x.lower(), axis="columns", inplace=True)
173
+
174
+ # Plotting Interface
175
+ @property
176
+ def skip_plot_vars(self) -> list:
177
+ """accesses '_skip_plot_vars' if it exists, otherwise returns empty list"""
178
+ if hasattr(self, "_skip_plot_vars"):
179
+ return [var.lower() for var in self._skip_plot_vars]
180
+ return []
181
+
182
+ @property
183
+ def dataframe(self):
184
+ """returns the dataframe"""
185
+ raise NotImplementedError("must be implemented in subclass")
@@ -0,0 +1,116 @@
1
+ # Auto-install datastores dependencies from pyproject.toml
2
+ import sys
3
+ import subprocess
4
+ import importlib.util
5
+ from pathlib import Path
6
+
7
+
8
+ def get_project_root():
9
+ """Find the project root containing pyproject.toml"""
10
+ current = Path(__file__).parent
11
+ while current.parent != current:
12
+ if (current / "pyproject.toml").exists():
13
+ return current
14
+ current = current.parent
15
+ return None
16
+
17
+
18
+ def install_optional_dependencies(group_name="database"):
19
+ """Install optional dependencies for a specific group"""
20
+ project_root = get_project_root()
21
+ if not project_root:
22
+ print("Could not find pyproject.toml. Manual installation required:")
23
+ print(f"pip install engforge[{group_name}]")
24
+ return False
25
+
26
+ try:
27
+ # Try to import tomllib (Python 3.11+) or tomli (fallback)
28
+ try:
29
+ import tomllib
30
+ except ImportError:
31
+ import tomli as tomllib
32
+
33
+ # Read pyproject.toml
34
+ with open(project_root / "pyproject.toml", "rb") as f:
35
+ config = tomllib.load(f)
36
+
37
+ optional_deps = config.get("project", {}).get("optional-dependencies", {})
38
+ if group_name not in optional_deps:
39
+ print(f"No optional dependency group '{group_name}' found")
40
+ return False
41
+
42
+ # Install the optional dependencies
43
+ package_name = config.get("project", {}).get("name", "engforge")
44
+ install_spec = f"{package_name}[{group_name}]"
45
+
46
+ print(f"Installing {install_spec}...")
47
+ result = subprocess.run(
48
+ [sys.executable, "-m", "pip", "install", install_spec],
49
+ capture_output=True,
50
+ text=True,
51
+ )
52
+
53
+ if result.returncode == 0:
54
+ print(f"✅ Successfully installed {install_spec}")
55
+ return True
56
+ else:
57
+ print(f"❌ Failed to install {install_spec}")
58
+ print(f"Error: {result.stderr}")
59
+ return False
60
+
61
+ except Exception as e:
62
+ print(f"Error during auto-installation: {e}")
63
+ print(f"Please install manually: pip install engforge[{group_name}]")
64
+ return False
65
+
66
+
67
+ def check_and_install_datastores():
68
+ """Check if datastores dependencies are available, auto-install if needed"""
69
+ try:
70
+ import engforge.datastores.data
71
+
72
+ return True
73
+ except ImportError as e:
74
+ print(f"Datastores dependencies not available: {e}")
75
+ print("")
76
+
77
+ # Check if we're in development mode (editable install) and interactive
78
+ project_root = get_project_root()
79
+ is_interactive = hasattr(sys, "ps1") or sys.stdout.isatty()
80
+
81
+ if (
82
+ project_root
83
+ and (project_root / "pyproject.toml").exists()
84
+ and is_interactive
85
+ ):
86
+ try:
87
+ answer = (
88
+ input("Auto-install database dependencies? (y/N): ").strip().lower()
89
+ )
90
+ if answer in ["y", "yes"]:
91
+ if install_optional_dependencies("database"):
92
+ print(
93
+ "Please restart your Python session to use the newly installed dependencies."
94
+ )
95
+ return False
96
+ else:
97
+ print("Auto-installation failed. Install manually with:")
98
+ print("pip install engforge[database]")
99
+ return False
100
+ else:
101
+ print("Install database dependencies with:")
102
+ print("pip install engforge[database]")
103
+ return False
104
+ except (EOFError, KeyboardInterrupt):
105
+ print("Install database dependencies with:")
106
+ print("pip install engforge[database]")
107
+ return False
108
+ else:
109
+ print("Install database dependencies with:")
110
+ print("pip install engforge[database]")
111
+ return False
112
+
113
+
114
+ # Auto-check and install on import
115
+ if not check_and_install_datastores():
116
+ pass # Dependencies not available, but user has been informed