steer-core 0.1.18__py3-none-any.whl → 0.1.20__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.
Binary file
@@ -1,19 +1,16 @@
1
1
  from functools import wraps
2
2
 
3
-
4
3
  def calculate_half_cell_curve(func):
5
4
  """
6
5
  Decorator to recalculate half-cell curve properties after a method call.
7
6
  This is useful for methods that modify the half-cell curve data.
8
7
  """
9
-
10
8
  @wraps(func)
11
9
  def wrapper(self, *args, **kwargs):
12
10
  result = func(self, *args, **kwargs)
13
- if hasattr(self, "_update_properties") and self._update_properties:
11
+ if hasattr(self, '_update_properties') and self._update_properties:
14
12
  self._calculate_half_cell_curve()
15
13
  return result
16
-
17
14
  return wrapper
18
15
 
19
16
 
@@ -22,12 +19,11 @@ def calculate_half_cell_curves_properties(func):
22
19
  Decorator to recalculate half-cell curves properties after a method call.
23
20
  This is useful for methods that modify the half-cell curves data.
24
21
  """
25
-
26
22
  @wraps(func)
27
23
  def wrapper(self, *args, **kwargs):
28
24
  result = func(self, *args, **kwargs)
29
- if hasattr(self, "_update_properties") and self._update_properties:
25
+ if hasattr(self, '_update_properties') and self._update_properties:
30
26
  self._calculate_half_cell_curves_properties()
31
27
  return result
32
-
33
28
  return wrapper
29
+
@@ -31,3 +31,4 @@ def calculate_all_properties(func):
31
31
  return result
32
32
 
33
33
  return wrapper
34
+
@@ -0,0 +1,164 @@
1
+
2
+
3
+ import numpy as np
4
+
5
+
6
+ class DunderMixin:
7
+
8
+ def _get_comparable_properties(self):
9
+ """Get all comparable properties from the class hierarchy."""
10
+ properties = []
11
+ for cls in self.__class__.__mro__:
12
+ for name, value in cls.__dict__.items():
13
+ if isinstance(value, property):
14
+ if not self._should_exclude_property(name):
15
+ properties.append(name)
16
+ return properties
17
+
18
+ def _should_exclude_property(self, name):
19
+ """Check if a property should be excluded from comparison."""
20
+ return (
21
+ name.endswith('_trace') or
22
+ name.endswith('_range') or
23
+ name in {'last_updated', 'properties'}
24
+ )
25
+
26
+ def _is_plotly_trace(self, obj):
27
+ """Check if object is a Plotly trace object."""
28
+ return (
29
+ hasattr(obj, '__module__') and
30
+ obj.__module__ and
31
+ obj.__module__.startswith('plotly.graph_objs')
32
+ )
33
+
34
+ def _compare_none_values(self, self_value, other_value):
35
+ """Compare None values. Returns (should_continue, result)."""
36
+ if self_value is None and other_value is None:
37
+ return True, True # Continue, values are equal
38
+ elif self_value is None or other_value is None:
39
+ return False, False # Stop, values are not equal
40
+ return True, None # Continue, not None values
41
+
42
+ def _compare_plotly_traces(self, self_value, other_value):
43
+ """Compare Plotly trace objects. Returns (should_continue, result)."""
44
+ if self._is_plotly_trace(self_value) or self._is_plotly_trace(other_value):
45
+ return True, True # Skip Plotly traces, continue
46
+ return True, None # Continue, not Plotly traces
47
+
48
+ def _compare_numpy_arrays(self, self_value, other_value):
49
+ """Compare NumPy arrays. Returns (should_continue, result)."""
50
+ if isinstance(self_value, np.ndarray) and isinstance(other_value, np.ndarray):
51
+ return False, np.array_equal(self_value, other_value, equal_nan=True)
52
+ elif isinstance(self_value, np.ndarray) or isinstance(other_value, np.ndarray):
53
+ return False, False # One is numpy array, other is not
54
+ return True, None # Continue, not numpy arrays
55
+
56
+ def _compare_dataframes(self, self_value, other_value):
57
+ """Compare pandas DataFrames/Series. Returns (should_continue, result)."""
58
+ if hasattr(self_value, 'equals') and hasattr(other_value, 'equals'):
59
+ return False, self_value.equals(other_value)
60
+ elif hasattr(self_value, 'equals') or hasattr(other_value, 'equals'):
61
+ return False, False # Only one is a DataFrame/Series
62
+ return True, None # Continue, not DataFrames
63
+
64
+ def _compare_dictionaries(self, self_value, other_value):
65
+ """Compare dictionaries by comparing keys and values separately. Returns (should_continue, result)."""
66
+ if isinstance(self_value, dict) and isinstance(other_value, dict):
67
+ # Compare keys first (order-independent)
68
+ if list(self_value.keys()) == list(other_value.keys()) and list(self_value.values()) == list(other_value.values()):
69
+ return False, True # Quick path: both keys and values match in order
70
+
71
+ # Compare values for each key
72
+ for key in self_value.keys():
73
+ if self_value[key] != other_value[key]:
74
+ return False, False
75
+
76
+ return False, True # Dictionaries are equal
77
+ elif isinstance(self_value, dict) or isinstance(other_value, dict):
78
+ return False, False # One is dict, other is not
79
+ return True, None # Continue, not dictionaries
80
+
81
+ def _compare_sequences(self, self_value, other_value):
82
+ """Compare lists and tuples. Returns (should_continue, result)."""
83
+ if isinstance(self_value, (list, tuple)) and isinstance(other_value, (list, tuple)):
84
+ return False, type(self_value) == type(other_value) and self_value == other_value
85
+ elif isinstance(self_value, (list, tuple)) or isinstance(other_value, (list, tuple)):
86
+ return False, False # One is sequence, other is not
87
+ return True, None # Continue, not sequences
88
+
89
+ def _compare_other_types(self, self_value, other_value):
90
+ """Compare all other types. Returns (should_continue, result)."""
91
+ return False, self_value == other_value
92
+
93
+ def __eq__(self, other):
94
+ """
95
+ Compare two instances based on all their @property decorated attributes.
96
+
97
+ Returns True if all properties have equal values, False otherwise.
98
+ Returns False if other is not an instance of the same class.
99
+ """
100
+ # Quick identity check first (performance optimization)
101
+ if self is other:
102
+ return True
103
+
104
+ # Check if other is the same type
105
+ if type(other) != type(self):
106
+ return False
107
+
108
+ # Cache properties to avoid repeated computation (performance optimization)
109
+ if not hasattr(self, '_cached_properties'):
110
+ self._cached_properties = self._get_comparable_properties()
111
+
112
+ # Define comparison methods in order of priority/frequency
113
+ comparison_methods = [
114
+ self._compare_none_values,
115
+ self._compare_plotly_traces,
116
+ self._compare_numpy_arrays,
117
+ self._compare_dataframes,
118
+ self._compare_dictionaries,
119
+ self._compare_sequences,
120
+ self._compare_other_types,
121
+ ]
122
+
123
+ # Compare all property values
124
+ for prop_name in self._cached_properties:
125
+
126
+ try:
127
+ self_value = getattr(self, prop_name)
128
+ other_value = getattr(other, prop_name)
129
+
130
+ # Execute comparison methods until one handles the values
131
+ for method in comparison_methods:
132
+ should_continue, result = method(self_value, other_value)
133
+ if not should_continue:
134
+ if not result:
135
+ return False
136
+ break # Values are equal, continue to next property
137
+
138
+ except (AttributeError, Exception):
139
+ # If property doesn't exist or comparison fails
140
+ return False
141
+
142
+ return True
143
+
144
+ def __hash__(self):
145
+ """
146
+ Simple, robust hash based on object identity.
147
+
148
+ Uses id() for a fast, guaranteed-unique hash that won't fail.
149
+ Objects are only equal if they're the same instance.
150
+ """
151
+ return hash(id(self))
152
+
153
+ def __str__(self):
154
+ """
155
+ String representation of the instance showing all @property decorated attributes and their values.
156
+ """
157
+ return f"{self.__class__.__name__}, {self.__name__}"
158
+
159
+ def __repr__(self):
160
+ """
161
+ Official string representation of the instance.
162
+ """
163
+ return self.__str__()
164
+
@@ -1,12 +1,37 @@
1
1
  from typing import Type
2
2
  import pandas as pd
3
3
  import numpy as np
4
+ import plotly.graph_objects as go
4
5
 
5
6
 
6
7
  ALLOWED_REFERENCE = ["Na/Na+", "Li/Li+"]
7
8
 
8
9
 
9
10
  class ValidationMixin:
11
+
12
+ @staticmethod
13
+ def validate_plotly_trace(value: object, name: str) -> None:
14
+ """
15
+ Validate that a value is a Plotly trace object.
16
+
17
+ Parameters
18
+ ----------
19
+ value : object
20
+ The value to validate.
21
+ name : str
22
+ The name of the parameter for error messages.
23
+
24
+ Raises
25
+ ------
26
+ TypeError
27
+ If the value is not a Plotly trace object.
28
+ """
29
+ return (
30
+ hasattr(value, '__module__') and
31
+ value.__module__ and
32
+ value.__module__.startswith('plotly.graph_objs')
33
+ )
34
+
10
35
  @staticmethod
11
36
  def validate_type(value: Type, expected_type: Type, name: str) -> None:
12
37
  """
@@ -27,9 +52,7 @@ class ValidationMixin:
27
52
  If the value is not of the expected type.
28
53
  """
29
54
  if not isinstance(value, expected_type):
30
- raise TypeError(
31
- f"{name} must be of type {expected_type.__name__}. Provided: {type(value).__name__}."
32
- )
55
+ raise TypeError(f"{name} must be of type {expected_type.__name__}. Provided: {type(value).__name__}.")
33
56
 
34
57
  @staticmethod
35
58
  def validate_percentage(value: float, name: str) -> None:
steer_core/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.18"
1
+ __version__ = "0.1.20"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steer-core
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: Modelling energy storage from cell to site - STEER OpenCell Design
5
5
  Home-page: https://github.com/nicholas9182/steer-core/
6
6
  Author: Nicholas Siemons
@@ -1,5 +1,5 @@
1
1
  steer_core/DataManager.py,sha256=06TrnBa4SLGvLeH2DacCxwGZ4zjLZslwNcwIlmfhxtA,10943
2
- steer_core/__init__.py,sha256=6BiuMUkhwQp6bzUZSF8np8F1NwCltEtK0sPBF__tepU,23
2
+ steer_core/__init__.py,sha256=8XalsVoLEfXslFvdtUEmkNOuYShzOzYOcFbgmOz1oSk,23
3
3
  steer_core/Apps/ContextManagers.py,sha256=-ImT0O8BdmPKOd_e7wS6AWJMMQme7nGDEUdxTi_wv8s,1870
4
4
  steer_core/Apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  steer_core/Apps/Components/MaterialSelectors.py,sha256=LBf2PvHkyAXUoZgEhoJCfo5shEfaloa6l3PeOJAakFk,35707
@@ -15,20 +15,21 @@ steer_core/Constants/Universal.py,sha256=5FWdrex5NiI2DResDmwO7GIvGN2B0DNtdlG1l-y
15
15
  steer_core/Constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  steer_core/ContextManagers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  steer_core/Data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- steer_core/Data/database.db,sha256=lCRyt-d92hq_yWMLmPVNvyjknMCznxkAf54eDX2iz90,13615104
18
+ steer_core/Data/database.db,sha256=y8cJv-W2T1EXoULbd8cT6AlN_yUIaTAkObX0pMDB4Oc,13615104
19
19
  steer_core/Decorators/Coordinates.py,sha256=MxUWXQNrR9Q0_p4gGAywS4qnPAztajJzSay1Cu6lCRQ,1441
20
- steer_core/Decorators/Electrochemical.py,sha256=YDXzYxrIpbMMViK2y56UHZHb6Y4bDSIeXSVeP8VtYr4,990
21
- steer_core/Decorators/General.py,sha256=y1azWe4Ja9BP5vHn2gqwIpHVs11xQ1WKdqOc9v_uegs,969
20
+ steer_core/Decorators/Electrochemical.py,sha256=nrNnTG4weyQOq1VLybjWWcbgGoth8ndvy3muN51xpwU,986
21
+ steer_core/Decorators/General.py,sha256=lc7YdvxU-JDo8b4kunVzSjxcB3_8C185458HrXQq-lk,970
22
22
  steer_core/Decorators/Objects.py,sha256=aYaRQBFgdSE0IB4QgBVfb6GhEPagoU6TRNrW_pOaqQI,506
23
23
  steer_core/Decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  steer_core/Mixins/Colors.py,sha256=vbo44Fr0oeziwHJ-tu7ojG-GzqFc2LBcT_hH4szvPFc,6796
25
25
  steer_core/Mixins/Coordinates.py,sha256=irdrrXIm7lmjMqgXyRXmu-x5swNQHhR7S0EZTBhvV8U,12824
26
26
  steer_core/Mixins/Data.py,sha256=2SXRIExCmd98N5JtNEFCQ9poi94fRF_GV5TNYjEGy6o,1363
27
+ steer_core/Mixins/Dunder.py,sha256=591oDGiRPdLH1bDIc1FUw33eeRtSc4pC7UbKEIGPm1I,7035
27
28
  steer_core/Mixins/Plotter.py,sha256=zYj-P9ryhSUe5mHoIyjasZSOnY5sDCFKx6u73E8rFZc,5424
28
- steer_core/Mixins/Serializer.py,sha256=VC3sqfPMR8BxBC7WaDO6cCDpVAgLV8MKAVZtiX953gQ,1016
29
- steer_core/Mixins/TypeChecker.py,sha256=YXvM3s0NIMGf8Dyk7xWgEMUak-tkuPxNK7ZgmefRUoE,7577
29
+ steer_core/Mixins/TypeChecker.py,sha256=CIv-Tt-NECR55hbscBvqQnzzABflj4bHG2zYHV4kLwk,8199
30
30
  steer_core/Mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- steer_core-0.1.18.dist-info/METADATA,sha256=AjS23x19XFZMX6mWTixmSzAbvuEoo5s_MuRZLX6g0Yc,704
32
- steer_core-0.1.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- steer_core-0.1.18.dist-info/top_level.txt,sha256=6LFpGCSDE_SqRoT7raeM3Ax7KTBKQnyXLXxM9kXtw5M,11
34
- steer_core-0.1.18.dist-info/RECORD,,
31
+ steer_core/Mixins/serializer.py,sha256=VC3sqfPMR8BxBC7WaDO6cCDpVAgLV8MKAVZtiX953gQ,1016
32
+ steer_core-0.1.20.dist-info/METADATA,sha256=vfJQ4joPBy8fuJ_uCyFEeV4FkSIc3mRd63fMg9RCEdw,704
33
+ steer_core-0.1.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ steer_core-0.1.20.dist-info/top_level.txt,sha256=6LFpGCSDE_SqRoT7raeM3Ax7KTBKQnyXLXxM9kXtw5M,11
35
+ steer_core-0.1.20.dist-info/RECORD,,
File without changes