oikan 0.0.3.2__py3-none-any.whl → 0.0.3.3__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.
oikan/exceptions.py CHANGED
@@ -4,4 +4,28 @@ class OIKANError(Exception):
4
4
 
5
5
  class ModelNotFittedError(OIKANError):
6
6
  """Raised when a method requires a fitted model."""
7
- pass
7
+ pass
8
+
9
+ class InvalidParameterError(OIKANError):
10
+ """Raised when an invalid parameter value is provided."""
11
+ pass
12
+
13
+ class DataDimensionError(OIKANError):
14
+ """Raised when input data has incorrect dimensions."""
15
+ pass
16
+
17
+ class NumericalInstabilityError(OIKANError):
18
+ """Raised when numerical computations become unstable."""
19
+ pass
20
+
21
+ class FeatureExtractionError(OIKANError):
22
+ """Raised when feature extraction or transformation fails."""
23
+ pass
24
+
25
+ class ModelSerializationError(OIKANError):
26
+ """Raised when model saving/loading operations fail."""
27
+ pass
28
+
29
+ class ConvergenceError(OIKANError):
30
+ """Raised when the model fails to converge during training."""
31
+ pass
oikan/model.py CHANGED
@@ -3,13 +3,14 @@ import torch
3
3
  import torch.nn as nn
4
4
  import torch.optim as optim
5
5
  from sklearn.preprocessing import PolynomialFeatures
6
- from sklearn.linear_model import Lasso
6
+ from sklearn.linear_model import ElasticNet
7
7
  from abc import ABC, abstractmethod
8
8
  import json
9
9
  from .neural import TabularNet
10
10
  from .utils import evaluate_basis_functions, get_features_involved
11
11
  from sklearn.model_selection import train_test_split
12
12
  from sklearn.metrics import r2_score, accuracy_score
13
+ from .exceptions import *
13
14
  import sys
14
15
 
15
16
  class OIKAN(ABC):
@@ -30,6 +31,8 @@ class OIKAN(ABC):
30
31
  L1 regularization strength for Lasso in symbolic regression.
31
32
  sigma : float, optional (default=0.1)
32
33
  Standard deviation of Gaussian noise for data augmentation.
34
+ top_k : int, optional (default=5)
35
+ Number of top features to select in hierarchical symbolic regression.
33
36
  epochs : int, optional (default=100)
34
37
  Number of epochs for neural network training.
35
38
  lr : float, optional (default=0.001)
@@ -43,7 +46,28 @@ class OIKAN(ABC):
43
46
  """
44
47
  def __init__(self, hidden_sizes=[64, 64], activation='relu', augmentation_factor=10,
45
48
  polynomial_degree=2, alpha=0.1, sigma=0.1, epochs=100, lr=0.001, batch_size=32,
46
- verbose=False, evaluate_nn=False):
49
+ verbose=False, evaluate_nn=False, top_k=5):
50
+ if not isinstance(hidden_sizes, list) or not all(isinstance(x, int) and x > 0 for x in hidden_sizes):
51
+ raise InvalidParameterError("hidden_sizes must be a list of positive integers")
52
+ if activation not in ['relu', 'tanh', 'leaky_relu', 'elu', 'swish', 'gelu']:
53
+ raise InvalidParameterError(f"Unsupported activation function: {activation}")
54
+ if not isinstance(augmentation_factor, int) or augmentation_factor < 1:
55
+ raise InvalidParameterError("augmentation_factor must be a positive integer")
56
+ if not isinstance(polynomial_degree, int) or polynomial_degree < 1:
57
+ raise InvalidParameterError("polynomial_degree must be a positive integer")
58
+ if not isinstance(top_k, int) or top_k < 1:
59
+ raise InvalidParameterError("top_k must be a positive integer")
60
+ if not 0 < lr < 1:
61
+ raise InvalidParameterError("Learning rate must be between 0 and 1")
62
+ if not isinstance(batch_size, int) or batch_size < 1:
63
+ raise InvalidParameterError("batch_size must be a positive integer")
64
+ if not isinstance(epochs, int) or epochs < 1:
65
+ raise InvalidParameterError("epochs must be a positive integer")
66
+ if not 0 <= alpha <= 1:
67
+ raise InvalidParameterError("alpha must be between 0 and 1")
68
+ if sigma <= 0:
69
+ raise InvalidParameterError("sigma must be positive")
70
+
47
71
  self.hidden_sizes = hidden_sizes
48
72
  self.activation = activation
49
73
  self.augmentation_factor = augmentation_factor
@@ -55,6 +79,7 @@ class OIKAN(ABC):
55
79
  self.batch_size = batch_size
56
80
  self.verbose = verbose
57
81
  self.evaluate_nn = evaluate_nn
82
+ self.top_k = top_k
58
83
  self.neural_net = None
59
84
  self.symbolic_model = None
60
85
  self.evaluation_done = False
@@ -74,13 +99,13 @@ class OIKAN(ABC):
74
99
  basis_functions = self.symbolic_model['basis_functions']
75
100
  if 'coefficients' in self.symbolic_model:
76
101
  coefficients = self.symbolic_model['coefficients']
77
- formula = " + ".join([f"{coefficients[i]:.3f}*{basis_functions[i]}"
102
+ formula = " + ".join([f"{coefficients[i]:.5f}*{basis_functions[i]}"
78
103
  for i in range(len(coefficients)) if coefficients[i] != 0])
79
104
  return formula if formula else "0"
80
105
  else:
81
106
  formulas = []
82
107
  for c, coef in enumerate(self.symbolic_model['coefficients_list']):
83
- formula = " + ".join([f"{coef[i]:.3f}*{basis_functions[i]}"
108
+ formula = " + ".join([f"{coef[i]:.5f}*{basis_functions[i]}"
84
109
  for i in range(len(coef)) if coef[i] != 0])
85
110
  formulas.append(f"Class {self.classes_[c]}: {formula if formula else '0'}")
86
111
  return formulas
@@ -129,27 +154,33 @@ class OIKAN(ABC):
129
154
  File path to save the model. Should end with .json
130
155
  """
131
156
  if self.symbolic_model is None:
132
- raise ValueError("Model not fitted yet.")
133
-
157
+ raise ModelNotFittedError("Model must be fitted before saving")
158
+
134
159
  if not path.endswith('.json'):
135
160
  path = path + '.json'
136
-
137
- # Convert numpy arrays and other non-serializable types to lists
138
- model_data = {
139
- 'n_features': self.symbolic_model['n_features'],
140
- 'degree': self.symbolic_model['degree'],
141
- 'basis_functions': self.symbolic_model['basis_functions']
142
- }
143
161
 
144
- if 'coefficients' in self.symbolic_model:
145
- model_data['coefficients'] = self.symbolic_model['coefficients']
146
- else:
147
- model_data['coefficients_list'] = [coef for coef in self.symbolic_model['coefficients_list']]
148
- if hasattr(self, 'classes_'):
149
- model_data['classes'] = self.classes_.tolist()
162
+ try:
163
+ # Convert numpy arrays and other non-serializable types to lists
164
+ model_data = {
165
+ 'n_features': self.symbolic_model['n_features'],
166
+ 'degree': self.symbolic_model['degree'],
167
+ 'basis_functions': self.symbolic_model['basis_functions']
168
+ }
169
+
170
+ if 'coefficients' in self.symbolic_model:
171
+ model_data['coefficients'] = self.symbolic_model['coefficients']
172
+ else:
173
+ model_data['coefficients_list'] = [coef for coef in self.symbolic_model['coefficients_list']]
174
+ if hasattr(self, 'classes_'):
175
+ model_data['classes'] = self.classes_.tolist()
176
+
177
+ with open(path, 'w') as f:
178
+ json.dump(model_data, f, indent=2)
179
+ except Exception as e:
180
+ raise ModelSerializationError(f"Failed to save model: {str(e)}")
150
181
 
151
- with open(path, 'w') as f:
152
- json.dump(model_data, f, indent=2)
182
+ if self.verbose:
183
+ print(f"Model saved to {path}")
153
184
 
154
185
  def load(self, path):
155
186
  """
@@ -162,22 +193,28 @@ class OIKAN(ABC):
162
193
  """
163
194
  if not path.endswith('.json'):
164
195
  path = path + '.json'
196
+
197
+ try:
198
+ with open(path, 'r') as f:
199
+ model_data = json.load(f)
200
+
201
+ self.symbolic_model = {
202
+ 'n_features': model_data['n_features'],
203
+ 'degree': model_data['degree'],
204
+ 'basis_functions': model_data['basis_functions']
205
+ }
165
206
 
166
- with open(path, 'r') as f:
167
- model_data = json.load(f)
168
-
169
- self.symbolic_model = {
170
- 'n_features': model_data['n_features'],
171
- 'degree': model_data['degree'],
172
- 'basis_functions': model_data['basis_functions']
173
- }
207
+ if 'coefficients' in model_data:
208
+ self.symbolic_model['coefficients'] = model_data['coefficients']
209
+ else:
210
+ self.symbolic_model['coefficients_list'] = model_data['coefficients_list']
211
+ if 'classes' in model_data:
212
+ self.classes_ = np.array(model_data['classes'])
213
+ except Exception as e:
214
+ raise ModelSerializationError(f"Failed to load model: {str(e)}")
174
215
 
175
- if 'coefficients' in model_data:
176
- self.symbolic_model['coefficients'] = model_data['coefficients']
177
- else:
178
- self.symbolic_model['coefficients_list'] = model_data['coefficients_list']
179
- if 'classes' in model_data:
180
- self.classes_ = np.array(model_data['classes'])
216
+ if self.verbose:
217
+ print(f"Model loaded from {path}")
181
218
 
182
219
  def _evaluate_neural_net(self, X, y, output_size, loss_fn):
183
220
  """Evaluates neural network performance on train-test split."""
@@ -253,7 +290,6 @@ class OIKAN(ABC):
253
290
 
254
291
  def _generate_augmented_data(self, X):
255
292
  """Generates augmented data by adding Gaussian noise."""
256
- n_samples = X.shape[0]
257
293
  X_aug = []
258
294
  for _ in range(self.augmentation_factor):
259
295
  noise = np.random.normal(0, self.sigma, X.shape)
@@ -262,32 +298,102 @@ class OIKAN(ABC):
262
298
  return np.vstack(X_aug)
263
299
 
264
300
  def _perform_symbolic_regression(self, X, y):
265
- """Performs symbolic regression using polynomial features and Lasso."""
266
- poly = PolynomialFeatures(degree=self.polynomial_degree, include_bias=True)
267
- X_poly = poly.fit_transform(X)
268
- model = Lasso(alpha=self.alpha, fit_intercept=False)
269
- model.fit(X_poly, y)
301
+ """
302
+ Performs hierarchical symbolic regression using a two-stage approach.
303
+
304
+ Parameters:
305
+ -----------
306
+ X : array-like of shape (n_samples, n_features)
307
+ Input data.
308
+ y : array-like of shape (n_samples,) or (n_samples, n_classes)
309
+ Target values or logits.
310
+ """
311
+ n_features = X.shape[1]
312
+ self.top_k = min(self.top_k, n_features)
313
+
314
+ if self.top_k < 1:
315
+ raise InvalidParameterError("top_k must be at least 1")
316
+
317
+ if np.any(np.isnan(X)) or np.any(np.isnan(y)):
318
+ raise NumericalInstabilityError("Input data contains NaN values")
319
+
320
+ if np.any(np.isinf(X)) or np.any(np.isinf(y)):
321
+ raise NumericalInstabilityError("Input data contains infinite values")
322
+
323
+ # Stage 1: Coarse Model
324
+ coarse_degree = 2 # Fixed low degree for coarse model
325
+ poly_coarse = PolynomialFeatures(degree=coarse_degree, include_bias=True)
326
+ X_poly_coarse = poly_coarse.fit_transform(X)
327
+ model_coarse = ElasticNet(alpha=self.alpha, fit_intercept=False)
328
+ model_coarse.fit(X_poly_coarse, y)
329
+
330
+ # Compute feature importances for original features
331
+ basis_functions_coarse = poly_coarse.get_feature_names_out()
270
332
  if len(y.shape) == 1 or y.shape[1] == 1:
271
- coef = model.coef_.flatten()
272
- selected_indices = np.where(np.abs(coef) > 1e-6)[0]
333
+ coef_coarse = model_coarse.coef_.flatten()
334
+ else:
335
+ coef_coarse = np.sum(np.abs(model_coarse.coef_), axis=0)
336
+
337
+ importances = np.zeros(X.shape[1])
338
+ for i, func in enumerate(basis_functions_coarse):
339
+ features_involved = get_features_involved(func)
340
+ for idx in features_involved:
341
+ importances[idx] += np.abs(coef_coarse[i])
342
+
343
+ if np.all(importances == 0):
344
+ raise FeatureExtractionError("Failed to compute feature importances - all values are zero")
345
+
346
+ # Select top K features
347
+ top_k_indices = np.argsort(importances)[::-1][:self.top_k]
348
+
349
+ # Stage 2: Refined Model
350
+ # ~ generate additional non-linear features for top K features
351
+ additional_features = []
352
+ additional_names = []
353
+ for i in top_k_indices:
354
+ # Higher-degree polynomial
355
+ additional_features.append(X[:, i]**3)
356
+ additional_names.append(f'x{i}^3')
357
+ # Non-linear transformations
358
+ additional_features.append(np.log1p(np.abs(X[:, i])))
359
+ additional_names.append(f'log1p_x{i}')
360
+ additional_features.append(np.exp(np.clip(X[:, i], -10, 10)))
361
+ additional_names.append(f'exp_x{i}')
362
+ additional_features.append(np.sin(X[:, i]))
363
+ additional_names.append(f'sin_x{i}')
364
+
365
+ # Combine features
366
+ X_additional = np.column_stack(additional_features)
367
+ X_refined = np.hstack([X_poly_coarse, X_additional])
368
+ basis_functions_refined = list(basis_functions_coarse) + additional_names
369
+
370
+ # Fit refined model
371
+ model_refined = ElasticNet(alpha=self.alpha, fit_intercept=False)
372
+ model_refined.fit(X_refined, y)
373
+
374
+ # Store symbolic model
375
+ if len(y.shape) == 1 or y.shape[1] == 1:
376
+ # Regression
377
+ coef_refined = model_refined.coef_.flatten()
378
+ selected_indices = np.where(np.abs(coef_refined) > 1e-6)[0]
273
379
  self.symbolic_model = {
274
380
  'n_features': X.shape[1],
275
- 'degree': self.polynomial_degree,
276
- 'basis_functions': poly.get_feature_names_out()[selected_indices].tolist(),
277
- 'coefficients': coef[selected_indices].tolist()
381
+ 'degree': self.polynomial_degree,
382
+ 'basis_functions': [basis_functions_refined[i] for i in selected_indices],
383
+ 'coefficients': coef_refined[selected_indices].tolist()
278
384
  }
279
385
  else:
386
+ # Classification
280
387
  coefficients_list = []
281
- # Note: Using the same basis functions across classes for simplicity
282
388
  selected_indices = set()
283
389
  for c in range(y.shape[1]):
284
- coef = model.coef_[c]
390
+ coef = model_refined.coef_[c]
285
391
  indices = np.where(np.abs(coef) > 1e-6)[0]
286
392
  selected_indices.update(indices)
287
393
  selected_indices = list(selected_indices)
288
- basis_functions = poly.get_feature_names_out()[selected_indices].tolist()
394
+ basis_functions = [basis_functions_refined[i] for i in selected_indices]
289
395
  for c in range(y.shape[1]):
290
- coef = model.coef_[c]
396
+ coef = model_refined.coef_[c]
291
397
  coef_selected = coef[selected_indices].tolist()
292
398
  coefficients_list.append(coef_selected)
293
399
  self.symbolic_model = {
oikan/utils.py CHANGED
@@ -9,7 +9,7 @@ def evaluate_basis_functions(X, basis_functions, n_features):
9
9
  X : array-like of shape (n_samples, n_features)
10
10
  Input data.
11
11
  basis_functions : list
12
- List of basis function strings (e.g., '1', 'x0', 'x0^2', 'x0 x1').
12
+ List of basis function strings (e.g., '1', 'x0', 'x0^2', 'x0 x1', 'log1p_x0').
13
13
  n_features : int
14
14
  Number of input features.
15
15
 
@@ -22,15 +22,26 @@ def evaluate_basis_functions(X, basis_functions, n_features):
22
22
  for i, func in enumerate(basis_functions):
23
23
  if func == '1':
24
24
  X_transformed[:, i] = 1
25
+ elif func.startswith('log1p_x'):
26
+ idx = int(func.split('_')[1][1:])
27
+ X_transformed[:, i] = np.log1p(np.abs(X[:, idx]))
28
+ elif func.startswith('exp_x'):
29
+ idx = int(func.split('_')[1][1:])
30
+ X_transformed[:, i] = np.exp(np.clip(X[:, idx], -10, 10))
31
+ elif func.startswith('sin_x'):
32
+ idx = int(func.split('_')[1][1:])
33
+ X_transformed[:, i] = np.sin(X[:, idx])
25
34
  elif '^' in func:
26
35
  var, power = func.split('^')
27
36
  idx = int(var[1:])
28
37
  X_transformed[:, i] = X[:, idx] ** int(power)
29
38
  elif ' ' in func:
30
- var1, var2 = func.split(' ')
31
- idx1 = int(var1[1:])
32
- idx2 = int(var2[1:])
33
- X_transformed[:, i] = X[:, idx1] * X[:, idx2]
39
+ vars = func.split(' ')
40
+ result = np.ones(X.shape[0])
41
+ for var in vars:
42
+ idx = int(var[1:])
43
+ result *= X[:, idx]
44
+ X_transformed[:, i] = result
34
45
  else:
35
46
  idx = int(func[1:])
36
47
  X_transformed[:, i] = X[:, idx]
@@ -43,21 +54,29 @@ def get_features_involved(basis_function):
43
54
  Parameters:
44
55
  -----------
45
56
  basis_function : str
46
- String representation of the basis function, e.g., 'x0', 'x0^2', 'x0 x1'.
57
+ String representation of the basis function, e.g., 'x0', 'x0^2', 'x0 x1', 'log1p_x0'.
47
58
 
48
59
  Returns:
49
60
  --------
50
61
  set : Set of feature indices involved.
51
62
  """
52
- if basis_function == '1': # Constant term involves no features
63
+ if basis_function == '1':
53
64
  return set()
54
65
  features = set()
55
- for part in basis_function.split(): # Split by space for interaction terms
56
- if part.startswith('x'):
57
- if '^' in part: # Handle powers, e.g., 'x0^2'
58
- var = part.split('^')[0] # Take 'x0'
59
- else:
60
- var = part # Take 'x0' as is
61
- idx = int(var[1:]) # Extract index, e.g., 0
66
+ if '_' in basis_function: # Handle non-linear functions like 'log1p_x0'
67
+ parts = basis_function.split('_')
68
+ if len(parts) == 2 and parts[1].startswith('x'):
69
+ idx = int(parts[1][1:])
62
70
  features.add(idx)
71
+ elif '^' in basis_function: # Handle powers, e.g., 'x0^2'
72
+ var = basis_function.split('^')[0]
73
+ idx = int(var[1:])
74
+ features.add(idx)
75
+ elif ' ' in basis_function: # Handle interactions, e.g., 'x0 x1'
76
+ for part in basis_function.split():
77
+ idx = int(part[1:])
78
+ features.add(idx)
79
+ elif basis_function.startswith('x'):
80
+ idx = int(basis_function[1:])
81
+ features.add(idx)
63
82
  return features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oikan
3
- Version: 0.0.3.2
3
+ Version: 0.0.3.3
4
4
  Summary: OIKAN: Neuro-Symbolic ML for Scientific Discovery
5
5
  Author: Arman Zhalgasbayev
6
6
  License: MIT
@@ -57,7 +57,7 @@ OIKAN implements a modern interpretation of the Kolmogorov-Arnold Representation
57
57
 
58
58
  2. **Neural Implementation**: OIKAN uses a specialized architecture combining:
59
59
  - Feature transformation layers with interpretable basis functions
60
- - Symbolic regression for formula extraction
60
+ - Symbolic regression for formula extraction (ElasticNet-based)
61
61
  - Automatic pruning of insignificant terms
62
62
 
63
63
  ```python
@@ -76,15 +76,19 @@ OIKAN implements a modern interpretation of the Kolmogorov-Arnold Representation
76
76
  SYMBOLIC_FUNCTIONS = {
77
77
  'linear': 'x', # Direct relationships
78
78
  'quadratic': 'x^2', # Non-linear patterns
79
+ 'cubic': 'x^3', # Higher-order relationships
79
80
  'interaction': 'x_i x_j', # Feature interactions
80
- 'higher_order': 'x^n' # Polynomial terms
81
+ 'higher_order': 'x^n', # Polynomial terms
82
+ 'trigonometric': 'sin(x)', # Trigonometric functions
83
+ 'exponential': 'exp(x)', # Exponential growth
84
+ 'logarithmic': 'log(x)' # Logarithmic relationships
81
85
  }
82
86
  ```
83
87
 
84
88
  4. **Formula Extraction Process**:
85
89
  - Train neural network on raw data
86
90
  - Generate augmented samples for better coverage
87
- - Perform L1-regularized symbolic regression
91
+ - Perform L1-regularized symbolic regression (alpha)
88
92
  - Prune terms with coefficients below threshold
89
93
  - Export human-readable mathematical expressions
90
94
 
@@ -115,12 +119,14 @@ model = OIKANRegressor(
115
119
  activation='relu', # Activation function (other options: 'tanh', 'leaky_relu', 'elu', 'swish', 'gelu')
116
120
  augmentation_factor=5, # Augmentation factor for data generation
117
121
  polynomial_degree=2, # Degree of polynomial basis functions
118
- alpha=0.1, # L1 regularization strength
122
+ alpha=0.1, # L1 regularization strength (Symbolic regression)
119
123
  sigma=0.1, # Standard deviation of Gaussian noise for data augmentation
124
+ top_k=5, # Number of top features to select (Symbolic regression)
120
125
  epochs=100, # Number of training epochs
121
126
  lr=0.001, # Learning rate
122
127
  batch_size=32, # Batch size for training
123
- verbose=True # Verbose output during training
128
+ verbose=True, # Verbose output during training
129
+ evaluate_nn=True # Validate neural network performance before full process
124
130
  )
125
131
 
126
132
  # Fit the model
@@ -163,12 +169,14 @@ model = OIKANClassifier(
163
169
  activation='relu', # Activation function (other options: 'tanh', 'leaky_relu', 'elu', 'swish', 'gelu')
164
170
  augmentation_factor=10, # Augmentation factor for data generation
165
171
  polynomial_degree=2, # Degree of polynomial basis functions
166
- alpha=0.1, # L1 regularization strength
172
+ alpha=0.1, # L1 regularization strength (Symbolic regression)
167
173
  sigma=0.1, # Standard deviation of Gaussian noise for data augmentation
174
+ top_k=5, # Number of top features to select (Symbolic regression)
168
175
  epochs=100, # # Number of training epochs
169
176
  lr=0.001, # Learning rate
170
177
  batch_size=32, # Batch size for training
171
- verbose=True # Verbose output during training
178
+ verbose=True, # Verbose output during training
179
+ evaluate_nn=True # Validate neural network performance before full process
172
180
  )
173
181
 
174
182
  # Fit the model
@@ -0,0 +1,10 @@
1
+ oikan/__init__.py,sha256=zEzhm1GYLT4vNaIQ4CgZcNpUk3uo8SWnoaHYtHW_XSQ,628
2
+ oikan/exceptions.py,sha256=GhHWqy2Q5LVBcteTy4ngnqxr7FOoLNyD8dNt1kfRXyw,901
3
+ oikan/model.py,sha256=wvF_g1RcpYcQin_wOUiWEUeKJcQ8HyPtEm_5YrCeXFs,21946
4
+ oikan/neural.py,sha256=wxmGgzmtpwJ3lvH6u6D4i4BiAzg018czrIdw49phSCY,1558
5
+ oikan/utils.py,sha256=_FNhB_sIQfY-KsKRqvuKKVXNVZaAdpI5w8zPY_j_xJU,2898
6
+ oikan-0.0.3.3.dist-info/licenses/LICENSE,sha256=75ASVmU-XIpN-M4LbVmJ_ibgbzbvRLVti8FhnR0BTf8,1096
7
+ oikan-0.0.3.3.dist-info/METADATA,sha256=moaO5H0kXU-Gf_sV7tpt4VUgmTEys6dINlzr0yfDSUc,9055
8
+ oikan-0.0.3.3.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
9
+ oikan-0.0.3.3.dist-info/top_level.txt,sha256=XwnwKwTJddZwIvtrUsAz-l-58BJRj6HjAGWrfYi_3QY,6
10
+ oikan-0.0.3.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- oikan/__init__.py,sha256=zEzhm1GYLT4vNaIQ4CgZcNpUk3uo8SWnoaHYtHW_XSQ,628
2
- oikan/exceptions.py,sha256=Is0jG4apxO8QJQREIiJQYMjANYWibWeS-103q9KWbfg,192
3
- oikan/model.py,sha256=oZtx5uFxMj4q89ODKDBeTJ0whbqiMIR2tKwgmYVXHUY,16887
4
- oikan/neural.py,sha256=wxmGgzmtpwJ3lvH6u6D4i4BiAzg018czrIdw49phSCY,1558
5
- oikan/utils.py,sha256=xMGRa1qhn8BWn9UxpVeJIuGb-UvQmbjiFSsvAdF0bMU,2095
6
- oikan-0.0.3.2.dist-info/licenses/LICENSE,sha256=75ASVmU-XIpN-M4LbVmJ_ibgbzbvRLVti8FhnR0BTf8,1096
7
- oikan-0.0.3.2.dist-info/METADATA,sha256=PPsMSll3Ds6E9J3ZnXxo8Yno0ZsHrSb55kZ-035jJZE,8441
8
- oikan-0.0.3.2.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
9
- oikan-0.0.3.2.dist-info/top_level.txt,sha256=XwnwKwTJddZwIvtrUsAz-l-58BJRj6HjAGWrfYi_3QY,6
10
- oikan-0.0.3.2.dist-info/RECORD,,