torchsystem 0.1.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.
@@ -0,0 +1,7 @@
1
+ from torchsystem.aggregate import Aggregate
2
+ from torchsystem.loaders import Loaders
3
+ from torchsystem.storage import Models, Criterions, Optimizer, Datasets
4
+ from torchsystem.compiler import Compiler
5
+ from pybondi import Event, Command
6
+ from pybondi.session import Session
7
+ from pybondi.repository import Repository
@@ -0,0 +1,46 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+ from typing import Iterator
4
+ from typing import Protocol
5
+ from typing import overload
6
+ from typing import Callable
7
+ from torch import Tensor
8
+ from torch.nn import Module
9
+ from torch.utils.data import Dataset
10
+ from pybondi.aggregate import Root
11
+
12
+ class Loader(Protocol):
13
+ '''
14
+ Interface for the DataLoader class.
15
+ '''
16
+ dataset: Dataset
17
+
18
+ def __iter__(self) -> Iterator[Any]:...
19
+
20
+ @overload
21
+ def __iter__(self) -> Iterator[tuple[Tensor, Tensor]]:...
22
+
23
+
24
+ class Aggregate(Module, ABC):
25
+
26
+ def __init__(self, id: Any):
27
+ super().__init__()
28
+ self.root = Root(id=id)
29
+ self.epoch = 0
30
+
31
+ @property
32
+ def phase(self) -> str:
33
+ return 'train' if self.training else 'evaluation'
34
+
35
+ @phase.setter
36
+ def phase(self, value: str):
37
+ self.train() if value == 'train' else self.eval()
38
+
39
+ @abstractmethod
40
+ def fit(self, data: Loader, callback: Callable): ...
41
+
42
+ @abstractmethod
43
+ def evaluate(self, data: Loader, callback: Callable): ...
44
+
45
+ def iterate(self, data: Loader, callback: Callable):
46
+ self.fit(data, callback) if self.training else self.evaluate(data, callback)
@@ -0,0 +1,51 @@
1
+ from torch import Tensor
2
+ from pybondi.callbacks import Callback
3
+ from torchsystem.callbacks.metrics import Metric, accuracy, predictions
4
+
5
+ class Average:
6
+ def __init__(self):
7
+ self.value = 0.0
8
+
9
+ def update(self, sample: int, value: float) -> float:
10
+ self.value = (self.value * (sample - 1) + value) / sample
11
+ return self.value
12
+
13
+ def reset(self):
14
+ self.value = 0.0
15
+
16
+ class Loss(Callback):
17
+ def __init__(self):
18
+ super().__init__()
19
+ self.epoch = 0
20
+ self.phase = None
21
+ self.average = Average()
22
+
23
+ def __call__(self, batch: int, loss: float, *args, **kwargs):
24
+ self.batch = batch
25
+ self.average.update(batch, loss)
26
+
27
+ def flush(self):
28
+ self.publisher.publish('result', Metric('loss', self.average.value, self.batch, self.epoch, self.phase))
29
+ self.average.reset()
30
+
31
+ def reset(self):
32
+ self.average.reset()
33
+
34
+
35
+ class Accuracy(Callback):
36
+ def __init__(self):
37
+ super().__init__()
38
+ self.epoch = 0
39
+ self.phase = None
40
+ self.average = Average()
41
+
42
+ def __call__(self, batch: int, _, output: Tensor, target: Tensor, *args, **kwargs):
43
+ self.batch = batch
44
+ self.average.update(batch, accuracy(predictions(output), target))
45
+
46
+ def flush(self):
47
+ self.publisher.publish('result', Metric('accuracy', self.average.value, self.batch, self.epoch, self.phase))
48
+ self.average.reset()
49
+
50
+ def reset(self):
51
+ self.average.reset()
@@ -0,0 +1,19 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+ from typing import Any
4
+ from torch import Tensor
5
+ from torch import argmax
6
+
7
+ @dataclass
8
+ class Metric:
9
+ name: str
10
+ value: Any
11
+ batch: int
12
+ epoch: int
13
+ phase: str
14
+
15
+ def accuracy(predictions: Tensor, target: Tensor) -> float:
16
+ return (predictions == target).float().mean().item()
17
+
18
+ def predictions(output: Tensor) -> Tensor:
19
+ return argmax(output, dim=1)
@@ -0,0 +1,89 @@
1
+ from typing import Sequence
2
+ from dataclasses import dataclass
3
+ from datetime import datetime, timezone
4
+ from pybondi import Command
5
+ from pybondi.callbacks import Callback
6
+ from torchsystem.aggregate import Aggregate
7
+ from torchsystem.aggregate import Loader
8
+ from torchsystem.events import Trained, Evaluated, Iterated
9
+
10
+ @dataclass
11
+ class Train(Command):
12
+ '''
13
+ The Train command is used to train the aggregate using the sequence of loaders provided.
14
+ It bumps the epoch after training the aggregate.
15
+ '''
16
+ aggregate: Aggregate
17
+ loaders: Sequence[Loader]
18
+ callback: Callback
19
+
20
+ def execute(self):
21
+ self.aggregate.phase = 'train'
22
+ self.callback.set('phase', self.aggregate.phase)
23
+ self.callback.set('epoch', self.aggregate.epoch)
24
+ start = datetime.now(timezone.utc)
25
+ for loader in self.loaders:
26
+ self.aggregate.fit(loader, self.callback)
27
+ end = datetime.now(timezone.utc)
28
+ self.aggregate.root.publish(event=Trained(
29
+ epoch=self.aggregate.epoch,
30
+ start=start,
31
+ end=end,
32
+ loaders=self.loaders,
33
+ aggregate=self.aggregate
34
+ ))
35
+ self.aggregate.epoch += 1
36
+
37
+ @dataclass
38
+ class Evaluate(Command):
39
+ '''
40
+ The Evaluate command is used to evaluate the aggregate using the sequence of loaders provided.
41
+ The aggregate is put on evaluation mode. It does not bump the epoch since it is not training the aggregate.
42
+ '''
43
+ aggregate: Aggregate
44
+ loaders: Sequence[Loader]
45
+ callback: Callback
46
+
47
+ def execute(self):
48
+ self.aggregate.phase = 'evaluation'
49
+ self.callback.set('phase', self.aggregate.phase)
50
+ self.callback.set('epoch', self.aggregate.epoch)
51
+ start = datetime.now(timezone.utc)
52
+ for loader in self.loaders:
53
+ self.aggregate.evaluate(loader, self.callback)
54
+ end = datetime.now(timezone.utc)
55
+ self.aggregate.root.publish(event=Evaluated(
56
+ epoch=self.aggregate.epoch,
57
+ start=start,
58
+ end=end,
59
+ loaders=self.loaders,
60
+ aggregate=self.aggregate
61
+ ))
62
+
63
+ @dataclass
64
+ class Iterate(Command):
65
+ '''
66
+ The Iterate command is used to iterate over the sequence of loaders provided.
67
+ It determines the phase of the aggregate and calls the fit or evaluate method accordingly.
68
+ After iterating over the loaders, it bumps the epoch.
69
+ '''
70
+ aggregate: Aggregate
71
+ loaders: Sequence[tuple[str, Loader]]
72
+ callback: Callback
73
+
74
+ def execute(self):
75
+ self.callback.set('epoch', self.aggregate.epoch)
76
+ start = datetime.now(timezone.utc)
77
+ for phase, loader in self.loaders:
78
+ self.aggregate.phase = phase
79
+ self.callback.set('phase', self.aggregate.phase )
80
+ self.aggregate.iterate(loader, self.callback)
81
+ end = datetime.now(timezone.utc)
82
+ self.aggregate.root.publish(event=Iterated(
83
+ epoch=self.aggregate.epoch,
84
+ start=start,
85
+ end=end,
86
+ loaders=self.loaders,
87
+ aggregate=self.aggregate
88
+ ))
89
+ self.aggregate.epoch += 1
@@ -0,0 +1,43 @@
1
+ from typing import Callable
2
+ from torch import compile
3
+ from pybondi.aggregate import Factory
4
+ from torchsystem.aggregate import Aggregate
5
+ from torchsystem.settings import Settings
6
+ from logging import getLogger
7
+
8
+ logger = getLogger(__name__)
9
+
10
+ class Compiler:
11
+ '''
12
+ The Compiler class is used to compile several modules into a single aggregate.
13
+
14
+ Parameters:
15
+ factory (Callable): A callable that returns an aggregate. Could be the aggregate's type
16
+ a function that initializes and returns an aggregate or a callable instance of a factory.
17
+ '''
18
+
19
+ def __init__(self, factory: Factory, settings: Settings = None):
20
+ self.settings = settings or Settings()
21
+ self.factory = factory
22
+
23
+ def compile(self, *args, **kwargs) -> Aggregate:
24
+ '''
25
+ Builds and compiles the aggregate using the factory provided.
26
+
27
+ Parameters:
28
+ *args: The positional arguments to pass to the factory.
29
+ **kwargs: The keyword arguments to pass to the factory.
30
+ '''
31
+ logger.info(f'Building and compiling the aggregate')
32
+ aggregate = self.factory(*args, **kwargs)
33
+ try:
34
+ compiled = compile(aggregate)
35
+ logger.info(f'Aggregate compiled successfully')
36
+ logger.info(f'Aggregate: {compiled}')
37
+ return compiled
38
+ except Exception as error:
39
+ logger.error(f'Error compiling the aggregate: {error}')
40
+ if self.settings.compilation.raise_on_error:
41
+ raise error
42
+ logger.info(f'Returning the uncompiled aggregate')
43
+ return aggregate
torchsystem/events.py ADDED
@@ -0,0 +1,41 @@
1
+ from typing import Sequence
2
+ from datetime import datetime
3
+ from dataclasses import dataclass
4
+ from pybondi import Event
5
+ from torchsystem.aggregate import Aggregate, Loader
6
+
7
+ @dataclass
8
+ class Trained(Event):
9
+ '''
10
+ The Trained event is used to signal that the aggregate has been trained.
11
+ over a sequence of loaders.
12
+ '''
13
+ epoch: int
14
+ start: datetime
15
+ end: datetime
16
+ loaders: Sequence[Loader]
17
+ aggregate: Aggregate
18
+
19
+ @dataclass
20
+ class Evaluated(Event):
21
+ '''
22
+ The Evaluated event is used to signal that the aggregate has been evaluated
23
+ over a sequence of loaders.
24
+ '''
25
+ epoch: int
26
+ start: datetime
27
+ end: datetime
28
+ loaders: Sequence[Loader]
29
+ aggregate: Aggregate
30
+
31
+ @dataclass
32
+ class Iterated(Event):
33
+ '''
34
+ The Iterated event is used to signal that the aggregate has been iterated
35
+ over a sequence of loaders.
36
+ '''
37
+ epoch: int
38
+ start: datetime
39
+ end: datetime
40
+ loaders: Sequence[tuple[str, Loader]]
41
+ aggregate: Aggregate
torchsystem/loaders.py ADDED
@@ -0,0 +1,53 @@
1
+ from typing import Iterator
2
+ from torch.utils.data import DataLoader
3
+ from torch.utils.data import Dataset
4
+ from torchsystem.aggregate import Loader
5
+ from torchsystem.settings import Settings
6
+ from mlregistry import Registry
7
+
8
+ INFRASTRUCTURE_PARAMETERS = {'dataset', 'pin_memory', 'pin_memory_device' ,'num_workers'}
9
+
10
+ class Loaders:
11
+ '''
12
+ This class acts as an iterable container for the data loaders. It allows decoupling the infrastructure
13
+ settings like devices from the domain settings of the dataloaders like batch size, shuffling, etc. When
14
+ you add a dataset to the loaders it will automatically register the dataset and it's dataloader in the
15
+ registry and add metadata to them.
16
+ '''
17
+
18
+ def __init__(self, settings: Settings = None, exclude_parameters: set[str] = INFRASTRUCTURE_PARAMETERS):
19
+ self.settings = settings or Settings()
20
+ self.registry = Registry(excluded_positions=[0], exclude_parameters=exclude_parameters)
21
+ self.registry.register(DataLoader, 'loader')
22
+ self.list = list[tuple[str, Loader]]()
23
+
24
+ def add(self, phase: str, dataset: Dataset, batch_size: int, shuffle: bool = False, settings: Settings | None = None, **kwargs):
25
+ '''
26
+ Add a dataset to the loaders. The dataset is added with the phase, batch size, and shuffle settings.
27
+
28
+ Parameters:
29
+ phase: str: The phase of the dataset. It can be 'train', 'validation', or 'test'.
30
+ dataset: Dataset: The dataset to be added to the loaders.
31
+ batch_size: int: The batch size of the dataset.
32
+ shuffle: bool: Whether to shuffle the dataset or not.
33
+ settings: Settings: The settings to be used for the dataloader. If None, the default settings are used.
34
+ **kwargs: Any: Additional keyword arguments to be passed to the DataLoader.
35
+ '''
36
+
37
+ settings = settings or self.settings
38
+ loader = DataLoader(
39
+ dataset,
40
+ batch_size=batch_size,
41
+ shuffle=shuffle,
42
+ pin_memory=self.settings.loaders.pin_memory,
43
+ pin_memory_device=self.settings.loaders.pin_memory_device,
44
+ num_workers=self.settings.loaders.num_of_workers,
45
+ **kwargs
46
+ )
47
+ self.list.append((phase, loader))
48
+
49
+ def __iter__(self) -> Iterator[tuple[str, Loader]]:
50
+ return iter(self.list)
51
+
52
+ def clear(self):
53
+ self.list.clear()
@@ -0,0 +1,26 @@
1
+ from pydantic import Field
2
+ from pydantic_settings import BaseSettings, SettingsConfigDict
3
+
4
+ class ModelSettings(BaseSettings):
5
+ device: str = Field(default='cpu')
6
+ model_config = SettingsConfigDict(env_prefix='MODEL_')
7
+
8
+ class CompilerSettings(BaseSettings):
9
+ raise_on_error: bool = Field(default=False)
10
+ model_config = SettingsConfigDict(env_prefix='COMPILATION_')
11
+
12
+ class LoaderSettings(BaseSettings):
13
+ pin_memory: bool = Field(default=False)
14
+ pin_memory_device: str = Field(default='')
15
+ num_of_workers: int = Field(default=0)
16
+ model_config = SettingsConfigDict(env_prefix='LOADER_')
17
+
18
+ class WeightsSettings(BaseSettings):
19
+ directory: str = Field(default='data/weights')
20
+ model_config = SettingsConfigDict(env_prefix='WEIGHTS_')
21
+
22
+ class Settings(BaseSettings):
23
+ model: BaseSettings = Field(default_factory=ModelSettings)
24
+ loaders: LoaderSettings = Field(default_factory=LoaderSettings)
25
+ compilation: CompilerSettings = Field(default_factory=CompilerSettings)
26
+ weights: WeightsSettings = Field(default_factory=WeightsSettings)
torchsystem/storage.py ADDED
@@ -0,0 +1,88 @@
1
+ from os import path
2
+ from typing import Optional
3
+ from torch.nn import Module
4
+ from torch.optim import Optimizer
5
+ from torch.utils.data import Dataset
6
+ from torchsystem.settings import Settings
7
+ from torchsystem.weights import Weights
8
+ from mlregistry import Registry
9
+ from mlregistry import get_hash
10
+
11
+ class Storage[T]:
12
+ '''
13
+ Base class for storage classes. It is responsible for coordinating the registry and weights of objects.
14
+ '''
15
+ registry: Registry[T]
16
+ weights: Weights[T]
17
+ category: str
18
+
19
+ @classmethod
20
+ def register(cls, type: type):
21
+ return cls.registry.register(type, cls.category)
22
+
23
+ def get(self, name: str, *args, **kwargs) -> Optional[T]:
24
+ '''
25
+ Get an object from the registry and restore it's weights if available.
26
+
27
+ Parameters:
28
+ name (str): The name of the object.
29
+ *args: Positional arguments for initializing the object.
30
+ **kwargs: Keyword arguments for initializing the object.
31
+ '''
32
+
33
+ if not name in self.registry.keys():
34
+ return None
35
+ object = self.registry.get(name)(*args, **kwargs)
36
+ if hasattr(self, 'weights'):
37
+ self.weights.restore(object, f'{self.category}:{get_hash(object)}')
38
+ return object
39
+
40
+ def store(self, object: T):
41
+ '''
42
+ Store the weights of an object in a given category, an assertion error is raised if the object is not registered.
43
+
44
+ Parameters:
45
+ object (T): The object to store.
46
+ '''
47
+ assert object.__class__.__name__ in self.registry.keys(), f'{object.__class__.__name__} not registered in {self.category}'
48
+ if hasattr(self, 'weights'):
49
+ self.weights.store(object, f'{self.category}:{get_hash(object)}')
50
+
51
+ def restore(self, object: T):
52
+ '''
53
+ Restore the weights of an object from a given category, an assertion error is raised if the object is not registered.
54
+
55
+ Parameters:
56
+ object (T): The object to restore.
57
+ '''
58
+ assert object.__class__.__name__ in self.registry.keys(), f'{object.__class__.__name__} not registered in {self.category}'
59
+ if hasattr(self, 'weights'):
60
+ self.weights.restore(object, f'{self.category}:{get_hash(object)}')
61
+
62
+ class Models(Storage[Module]):
63
+ category = 'model'
64
+ registry = Registry()
65
+
66
+ def __init__(self, folder: str | None = None, settings: Settings = None):
67
+ self.settings = settings or Settings()
68
+ self.weights = Weights(path.join(self.settings.weights.directory, folder) if folder else self.settings.weights.directory)
69
+
70
+ class Criterions(Storage[Module]):
71
+ category = 'criterion'
72
+ registry = Registry()
73
+
74
+ def __init__(self, folder: str | None = None, settings: Settings = None):
75
+ self.settings = settings or Settings()
76
+ self.weights = Weights(path.join(self.settings.weights.directory, folder) if folder else self.settings.weights.directory)
77
+
78
+ class Optimizers(Storage[Optimizer]):
79
+ category = 'optimizer'
80
+ registry = Registry(excluded_positions=[0], exclude_parameters={'params'})
81
+
82
+ def __init__(self, folder: str | None = None, settings: Settings = None):
83
+ self.settings = settings or Settings()
84
+ self.weights = Weights(path.join(self.settings.weights.directory, folder) if folder else self.settings.weights.directory)
85
+
86
+ class Datasets(Storage[Dataset]):
87
+ category = 'dataset'
88
+ registry = Registry(exclude_parameters={'root', 'download'})
torchsystem/weights.py ADDED
@@ -0,0 +1,51 @@
1
+ from os import makedirs
2
+ from os import path, makedirs
3
+ from logging import getLogger
4
+ from torch import save, load
5
+ from torch.nn import Module
6
+
7
+ logger = getLogger(__name__)
8
+
9
+ class Weights[T: Module]:
10
+ '''
11
+ Weights class is responsible for storing and restoring the weights of a module.
12
+
13
+ Args:
14
+ directory (str): The directory to store the weights.
15
+ '''
16
+ def __init__(self, directory: str):
17
+ self.location = directory
18
+ if not path.exists(self.location):
19
+ makedirs(self.location)
20
+
21
+
22
+ def store(self, module: T, filename: str):
23
+ '''
24
+ Store the weights of a module.
25
+
26
+ Parameters:
27
+ module (Module): The module to store the weights.
28
+ folder (str): The folder to store the weights.
29
+ filename (str): The filename to store the
30
+ '''
31
+ logger.info(f'Storing weights of {module.__class__.__name__} in {filename}.pth')
32
+ save(module.state_dict(), path.join(self.location, filename + '.pth'))
33
+ logger.info(f'Weights stored successfully')
34
+
35
+
36
+ def restore(self, module: T, filename: str):
37
+ '''
38
+ Restore the weights of a module.
39
+
40
+ Parameters:
41
+ module (Module): The module to restore the weights.
42
+ folder (str): The folder to restore the weights.
43
+ filename (str): The filename to restore the weights.
44
+ '''
45
+ logger.info(f'Restoring weights of {module.__class__.__name__} from {filename}.pth')
46
+ try:
47
+ state_dict = load(path.join(self.location, filename + '.pth'), weights_only=True)
48
+ module.load_state_dict(state_dict)
49
+ logger.info(f'Weights restored successfully')
50
+ except FileNotFoundError as error:
51
+ logger.warning(f'Error restoring weights: {error}')
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mapache Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: torchsystem
3
+ Version: 0.1.0
4
+ Summary: An IA training system, based on domain driven design and an event driven architecture, created on top of the pybondi library.
5
+ License: MIT
6
+ Author: Eric Cardozo
7
+ Author-email: eric.m.cardozo@mi.unc.edu.ar
8
+ Requires-Python: >=3.12,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: mlregistry (>=1.0.0,<2.0.0)
14
+ Requires-Dist: pybondi (>=0.2.3,<0.3.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # torch-system
18
+ An IA training system, created using domain driven design and an event driven architecture.
19
+
@@ -0,0 +1,15 @@
1
+ torchsystem/__init__.py,sha256=-_IuDIA71zFptbspHgN5gWW4zPsCAAe1AThw9G4EqnE,310
2
+ torchsystem/aggregate.py,sha256=YVNK3pqcyhMiRxhdwQmbZkMphfEigk5i43auKmyTwLo,1199
3
+ torchsystem/callbacks/average.py,sha256=1p06GBZl3uIBF87DvKa5CLoajHTBdMkVEpXN8ivKqdM,1498
4
+ torchsystem/callbacks/metrics.py,sha256=GanvN2_zHxucOvVWpB5YFlkS_lsS8rKos3vqdIs8-tM,429
5
+ torchsystem/commands.py,sha256=9DnC3czK3ngPfz-_6iXAKYS4-OlQ1fsZrZ0zAO60P2E,3130
6
+ torchsystem/compiler.py,sha256=fmUgkA7m3HTxc8cDWOk5OtHG8kiVAHbMVMlDqBsCeXY,1605
7
+ torchsystem/events.py,sha256=Tm8BEGjrBIDVX06pkemD-b4BM14hzvWfXzNW2bpPyU0,993
8
+ torchsystem/loaders.py,sha256=e2QtQ79Nc30Zm-DGxfwmO8ZfjJoOEyWhyt01AHMYsZM,2408
9
+ torchsystem/settings.py,sha256=3--h06nWc0GcW3ehNDv9HLxW56GHxUtc9W7s20sYITY,1064
10
+ torchsystem/storage.py,sha256=8USWlA3M8B1JShUBXBQUsxoLkees-u-1ftfIMW1EVlM,3474
11
+ torchsystem/weights.py,sha256=6zoITMkjl9WC5BdH5L890-lwppoydRNRsGTnMii60q4,1806
12
+ torchsystem-0.1.0.dist-info/LICENSE,sha256=xaMmSaui1itQNp2WE6mT7zoqwyhLqqObOA5YW7BqXnc,1073
13
+ torchsystem-0.1.0.dist-info/METADATA,sha256=IsCJxZ9l5rCrlJqAldSwnWKYK9yl4Kft14t7YsUtlgQ,729
14
+ torchsystem-0.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
15
+ torchsystem-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any