Skip to content

Refactor/td integration #1437#1439

Open
Romankkl03 wants to merge 62 commits into
refactor/fedot_1.0.0from
refactor/td_integration
Open

Refactor/td integration #1437#1439
Romankkl03 wants to merge 62 commits into
refactor/fedot_1.0.0from
refactor/td_integration

Conversation

@Romankkl03

@Romankkl03 Romankkl03 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Context

сloses #1437

Romankk03 and others added 16 commits June 9, 2026 13:30
… parameters, sampling, and prediction intervals
…on with schema validation and error handling
@Romankkl03 Romankkl03 self-assigned this Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Code in this pull request still contains PEP8 errors, please write the /fix-pep8 command in the comments below to create commit with automatic fixes.

Comment last updated at Mon, 13 Jul 2026 21:41:17

Comment thread fedot/api/api_utils/api_params_repository.py Outdated
Comment thread fedot/api/api_utils/params.py Outdated
Comment on lines +56 to +60
self.tensor_data_config: Dict[str, Any] = resolve_tensor_data_config(
self.get('tensor_data_config'),
use_preprocessing_cache=self.get('use_preprocessing_cache', True),
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why after the parameters: dict = self._params_repository.check_and_set_default_params(input_params) ?

@Romankkl03 Romankkl03 Jun 11, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted it in 58778cb

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but why

self.tensor_data_config: Dict[str, Any] = resolve_tensor_data_config(
            self.get('tensor_data_config'),
            use_preprocessing_cache=self.get('use_preprocessing_cache', True),
        )

after the

parameters: dict = self._params_repository.check_and_set_default_params(input_params)
super().__init__(...)

if target is not None:
spec_kwargs['target'] = target

return TensorDataCreationRequest(backend_name=backend_name, spec_kwargs=spec_kwargs)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the TensorDataCreationRequest necessary?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, it one of the way for correct aggregation TDCreator's params. But may be refactored later.

_ALLOWED_KEYS: Set[str] = _USER_CONFIGURABLE_DATA_SPEC_KEYS | _CREATOR_ONLY_KEYS

# TODO romankuklo: add "cuda:n" to the supported backends
_SUPPORTED_BACKENDS = frozenset({'cpu', 'gpu'})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be initialized inside the Backend singleton

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added name normalization methods into Backend [66b1924]

Comment on lines +30 to +75
def validate_tensor_data_config(config: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""
Validate user-provided ``tensor_data_config`` for :class:`~fedot.api.api_utils.params.ApiParams`.

The config is a flat dictionary of options forwarded to
:meth:`~fedot.core.data.tensor_data.tensor_data_creator.TensorDataCreator.create`
(as ``DataSpec`` kwargs) plus ``backend_name``. Runtime values such as ``task``,
``state``, and ``target`` must not be set here — they are injected when data
is created during ``fit`` / ``predict``.

Args:
config: User config dictionary or ``None``.

Returns:
A shallow copy of the validated config, or ``None`` when *config* is ``None``.

Raises:
ValueError: If *config* is not a dict or contains unknown / forbidden keys.
"""
if config is None:
return None
if not isinstance(config, dict):
raise ValueError('"tensor_data_config" must be a dictionary or None.')

unknown_keys = set(config) - _ALLOWED_KEYS
if unknown_keys:
raise ValueError(
f'Unknown keys in "tensor_data_config": {sorted(unknown_keys)}'
)

forbidden_keys = set(config) & _RUNTIME_KEYS
if forbidden_keys:
raise ValueError(
'Keys reserved for runtime injection must not appear in '
f'"tensor_data_config": {sorted(forbidden_keys)}'
)

# TODO romankuklo: should remove from here?
normalized = dict(config)
if 'backend_name' in normalized:
normalized['backend_name'] = _normalize_backend_name(normalized['backend_name'])

if 'use_cache' in normalized and not isinstance(normalized['use_cache'], bool):
raise ValueError('"tensor_data_config.use_cache" must be a boolean.')

return normalized

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #1438 for new validation logic

Comment thread fedot/api/main.py Outdated
)


def _prepare_fit_context_td(self,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no usage of this method

Comment on lines +41 to +48
if n_rows <= 0:
raise ValueError("n_rows must be positive")

if max_ohe_width < 0:
raise ValueError("max_ohe_width must be non-negative")

if max_ohe_width_ratio < 0:
raise ValueError("max_ohe_width_ratio must be non-negative")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation through marshmallow

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use tuples within methods where applicable for (non-dirty) methods

Comment thread tests/api/api_utils/test_api_params.py Outdated
Comment on lines +51 to +58
def test_api_params_builds_tensor_data_config_field_on_init():
params = ApiParams({}, problem='classification', timeout=1)

assert params.tensor_data_config == {
'backend_name': 'cpu',
'use_cache': True,
}
assert params['tensor_data_config'] == params.tensor_data_config

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FAILED tests/api/api_utils/test_api_params.py::test_api_params_builds_tensor_data_config_field_on_init - KeyError: 'tensor_data_config'

self._log = default_log('InputAnalyzer')

def give_recommendations(self, input_data: Union[InputData, MultiModalData], input_params=None) \
def give_recommendations(self, input_data: Union[InputData, MultiModalData, TensorData], input_params=None) \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we remove the InputData support in this PR, or have you decided to keep it and address it in the next one?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to keep InputData before test comparing with TensorData. Furthermore, the vast part of pipeline use ArrayType yet.

Comment thread fedot/api/main.py Outdated
Comment on lines +574 to +581
def fit_tensor_data(self,
features: FeaturesType,
target: TargetType = 'target',
predefined_model: Union[str, Pipeline] = None) -> Pipeline:

MemoryAnalytics.start()

initial_timeout = self.params.timeout

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be better for fit_tensor_data method to work with TensorData not the features-target pair?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you’re suggesting that the creation TensorData and preprocessing (obligatory) stages should be left to the user?

Comment thread fedot/core/backend/backend.py Outdated
...
"""
DEFAULT_NAME = 'cpu'
GPU_ALIASES = frozenset({'gpu', 'cuda'})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gpus on m-series macs can also be used in mps, so it might be much better not to enforce cuda from gpu user-specs.
instead, it would be better to determine what GPU capabilities are available and assess which ones are compatible with FEDOT

Comment on lines 94 to 95
# TODO romankuklo: make features as obligatory field
features: Optional[torch.Tensor] = None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make features obligatory in this PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 4709069

from fedot.core.operations.operation_parameters import OperationParameters


class TorchLinearClassifier:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be inherited or be a composite product of ModelImplementation, Operation, or Model - see which one suits better

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 4709069

Comment thread fedot/core/operations/operation.py Outdated
Comment on lines +206 to +211
def fit_tensordata(self,
params: Optional[Union[OperationParameters, dict]],
data: TensorData,
predictions_cache: Optional[PredictionsCache] = None,
fold_id: Optional[int] = None,
descriptive_id: Optional[str] = None):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here, it's TensorData (see the comment in main.py regarding a similar consideration)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But after automatic TensorData creating

Comment thread fedot/core/pipelines/node.py Outdated
Comment on lines +236 to +296
def fit_tensordata(self,
tensor_data: TensorData,
predictions_cache: Optional[PredictionsCache] = None,
fold_id: Optional[int] = None) -> TensorData:
"""Runs training process in the node on TensorData.

Slice 1 supports only primary nodes without parents (e.g. single predefined model).
"""
self.log.debug(
f'Trying to fit pipeline node with operation: {self.operation} on TensorData')
tensor_data = self._get_tensor_data(
tensor_data=tensor_data, parent_operation='fit')

if self.fitted_operation is None:
with Timer() as t:
self.fitted_operation, operation_predict = self.operation.fit_tensordata(
params=self._parameters,
data=tensor_data,
predictions_cache=predictions_cache,
fold_id=fold_id,
descriptive_id=self.descriptive_id,
)
self.fit_time_in_seconds = round(t.seconds_from_start, 3)
else:
operation_predict = self.operation.predict_tensordata(
fitted_operation=self.fitted_operation,
data=tensor_data,
params=self._parameters,
predictions_cache=predictions_cache,
fold_id=fold_id,
descriptive_id=self.descriptive_id,
is_fit_stage=True,
)

if should_update_node_parameters(self.operation.operation_type, self.operation.metadata.tags):
self.update_params()
return operation_predict

def predict_tensordata(self,
tensor_data: TensorData,
output_mode: str = 'default',
predictions_cache: Optional[PredictionsCache] = None,
fold_id: Optional[int] = None) -> TensorData:
"""Runs prediction process in the node on TensorData."""
self.log.debug(
f'Trying to predict pipeline node with operation: {self.operation} on TensorData')
tensor_data = self._get_tensor_data(
tensor_data=tensor_data, parent_operation='predict')
with Timer() as t:
operation_predict = self.operation.predict_tensordata(
fitted_operation=self.fitted_operation,
data=tensor_data,
params=self._parameters,
output_mode=output_mode,
predictions_cache=predictions_cache,
fold_id=fold_id,
descriptive_id=self.descriptive_id,
is_fit_stage=False,
)
self.inference_time_in_seconds = round(t.seconds_from_start, 3)
return operation_predict

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuse some methods, clean them up, and replace the * methods with your *_for_tensordata versions

@@ -1,4 +1,8 @@
{
"torch_linear": {
"learning_rate": 0.05,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on LR scheduler?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b411d60

from fedot.core.operations.evaluation.operation_implementations.models.torch import TorchLinearClassifier


class TorchTabularClassificationStrategy(EvaluationStrategy):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only tabular? can the Torch Linear model NOT be data-agnostic?

method=step.method.value if hasattr(step.method, "value") else str(step.method),
features_idx=step.features_idx,
)
else:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be logged?

@Lopa10ko
Lopa10ko marked this pull request as ready for review June 29, 2026 15:53
logger.info(
f"Using CUDA stack with cudf/cupy and torch.device('{normalized_name}'). ",
)
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not urgent, but from a style point of view, rather difficult to read code. frequent checks, ornate code... try to simplify the logic of checks using strategy or factory patterns

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe dict with strategies

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, added dict with strategies: c44ce8c

Comment thread fedot/core/backend/schemas.py Outdated
)

normalized = value.strip().lower()
if normalized in ('cpu', 'gpu', 'cuda', 'mps'):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no magic set here - ('cpu', 'gpu', 'cuda', 'mps') should be initialized once as a backend layer constant SUPPORTED_BACKENDS or smth simillar

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: c44ce8c

Comment thread fedot/core/backend/schemas.py Outdated
from fedot.validation.context import ValidationContext

BACKEND_CUDA_DEVICE_PATTERN = re.compile(r'^cuda:\d+$')
BACKEND_SUPPORTED_NAMES_HINT = 'cpu, gpu, cuda, mps, cuda:<device_index>'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be built in-place (in 24th line) from SUPPORTED_BACKENDS constant set (see comment at 28th line)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: c44ce8c

Comment on lines 159 to 405
@@ -391,7 +400,8 @@ def _parse_tasks(raw: Optional[str]) -> Optional[list[str]]:


def main() -> None:
parser = argparse.ArgumentParser(description="Run OpenML benchmark with FEDOT TabICL and update foundational column.")
parser = argparse.ArgumentParser(
description="Run OpenML benchmark with FEDOT TabICL and update foundational column.")
parser.add_argument("--classification-suite", type=int, default=DEFAULT_CLASSIFICATION_SUITE)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please configure your local linter to 120-symbols of code in one line, no need to format legacy code in that way



def default_repository_name_for_data(data) -> str:
# TODO @romankuklo: delete multi_ts?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk, could we support multi_ts through TensorData?
multivariate ts and multi_ts are not the same thing - seek guidance from @v1docq

return valid_builders or [self.assumptions_generator.fallback_builder(self.ops_filter)]


# TODO: refactor for TensorData if needed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still a question to research

operations_cache: Optional[OperationsCache] = None,
preprocessing_cache: Optional[PreprocessingCache] = None,
eval_n_jobs: int = -1) -> Pipeline:
def fit_assumption_and_check_correctness_with_tensordata(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why *_with_tensordata again? no point at providing 2 different signatures here and everywhere else, just refactor the old methods

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 879b3e8

*initial_nodes, use_input_preprocessing=use_input_preprocessing)

@classmethod
def builder_for_tensordata(cls,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here also, just change logic inside actual builder_for_data, do not introduce a separate builder_for_tensordata - this becomes more difficult to handle in other PRs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just comment the old methods out for now, and introduce the new logic, but not as a separate *tensordata methods

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 879b3e8

Comment on lines +285 to +307
def test_regression_metric_tensordata_implementation(metric_class, expected_value):
target = np.array([1.0, 2.0, 4.0, 8.0])
prediction = np.array([1.1, 1.8, 4.5, 7.5])
features = np.array([0.8, 1.2, 2.1, 4.1])
task = Task(TaskTypesEnum.regression)

tensor_reference, tensor_prediction = _build_tensor_metric_data(
target, prediction, task, features=features)

tensor_value = metric_class.metric(tensor_reference, tensor_prediction)

assert np.isclose(tensor_value, expected_value, rtol=1e-6, atol=1e-6)


@pytest.mark.parametrize(
'metric_class, prediction, expected_value',
[
(Accuracy, np.array([0, 1, 1, 0, 1]), -0.6),
(F1, np.array([0, 1, 1, 0, 1]), -0.5),
(Precision, np.array([0, 1, 1, 0, 1]), -0.5),
(ROCAUC, np.array([0.1, 0.8, 0.7, 0.4, 0.9]), -0.6666666666666666),
(Logloss, np.array([0.1, 0.8, 0.7, 0.4, 0.9]), 0.6186249239125281),
],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no point in writing tests inside test dir, since it tests legacy code, need rewriting in tests instead and deleting the old ones in test one by one

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be done later

fold_id=fold_id,
descriptive_id=self.descriptive_id)

# Update parameters after operation fitting (they can be corrected)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not delete previous comments and google-style docstrings for methods

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return comment: 4bc374d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants