From 731087964bf2aa3a4f206f9d24286453bbca31ac Mon Sep 17 00:00:00 2001 From: Jakob Wagner Date: Thu, 25 Jul 2024 14:25:16 +0200 Subject: [PATCH 1/8] add tests --- tests/operators/fixtures.py | 5 +++ tests/operators/test_deep_cat_operator.py | 49 +++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/operators/test_deep_cat_operator.py diff --git a/tests/operators/fixtures.py b/tests/operators/fixtures.py index 0b8ad0e5..a54a3808 100644 --- a/tests/operators/fixtures.py +++ b/tests/operators/fixtures.py @@ -48,3 +48,8 @@ def random_shape_operator_datasets() -> List[OperatorDataset]: ) return datasets + + +@pytest.fixture(scope="session") +def random_operator_dataset(random_shape_operator_datasets) -> OperatorDataset: + return random_shape_operator_datasets[0] diff --git a/tests/operators/test_deep_cat_operator.py b/tests/operators/test_deep_cat_operator.py new file mode 100644 index 00000000..8f9db0bc --- /dev/null +++ b/tests/operators/test_deep_cat_operator.py @@ -0,0 +1,49 @@ +import pytest +from typing import List + +from continuiti.operators import DeepCatOperator +from continuiti.benchmarks.sine import SineBenchmark +from continuiti.trainer import Trainer +from continuiti.operators.losses import MSELoss +from continuiti.networks import DeepResidualNetwork + +from .util import get_shape_mismatches + + +@pytest.fixture(scope="module") +def dcos(random_shape_operator_datasets) -> List[DeepCatOperator]: + return [ + DeepCatOperator(dataset.shapes) for dataset in random_shape_operator_datasets + ] + + +class TestDeepCatOperator: + def test_can_initialize(self, random_operator_dataset): + operator = DeepCatOperator(random_operator_dataset.shapes) + + assert isinstance(operator, DeepCatOperator) + + def test_can_initialize_default_networks(self, random_operator_dataset): + operator = DeepCatOperator(shapes=random_operator_dataset.shapes) + + assert isinstance(operator.input_net, DeepResidualNetwork) + assert isinstance(operator.eval_net, DeepResidualNetwork) + assert isinstance(operator.cat_net, DeepResidualNetwork) + + def test_forward_shapes_correct(self, dcos, random_shape_operator_datasets): + assert get_shape_mismatches(dcos, random_shape_operator_datasets) == [] + + @pytest.mark.slow + def test_can_overfit(self): + # Data set + dataset = SineBenchmark(n_train=1).train_dataset + + # Operator + operator = DeepCatOperator(dataset.shapes) + + # Train + Trainer(operator).fit(dataset, tol=1e-2) + + # Check solution + x, u, y, v = dataset.x, dataset.u, dataset.y, dataset.v + assert MSELoss()(operator, x, u, y, v) < 1e-2 From acd1749ab97f581ae4649ec390b464f4be67422d Mon Sep 17 00:00:00 2001 From: Jakob Wagner Date: Thu, 25 Jul 2024 14:25:44 +0200 Subject: [PATCH 2/8] add deep cat operator --- src/continuiti/operators/__init__.py | 2 + src/continuiti/operators/deep_cat_operator.py | 115 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/continuiti/operators/deep_cat_operator.py diff --git a/src/continuiti/operators/__init__.py b/src/continuiti/operators/__init__.py index 66989e59..945e75d9 100644 --- a/src/continuiti/operators/__init__.py +++ b/src/continuiti/operators/__init__.py @@ -19,6 +19,7 @@ from .fno import FourierNeuralOperator from .shape import OperatorShapes from .cnn import ConvolutionalNeuralNetwork +from .deep_cat_operator import DeepCatOperator __all__ = [ "Operator", @@ -29,4 +30,5 @@ "DeepNeuralOperator", "FourierNeuralOperator", "ConvolutionalNeuralNetwork", + "DeepCatOperator", ] diff --git a/src/continuiti/operators/deep_cat_operator.py b/src/continuiti/operators/deep_cat_operator.py new file mode 100644 index 00000000..af2abf00 --- /dev/null +++ b/src/continuiti/operators/deep_cat_operator.py @@ -0,0 +1,115 @@ +""" +`continuiti.operators.deep_cat_operator` + +The DeepCatOperator architecture. +""" + +import torch +import torch.nn as nn +from typing import Optional +from math import ceil, prod +from .operator import Operator, OperatorShapes +from continuiti.networks import DeepResidualNetwork + + +class DeepCatOperator(Operator): + """Deep Cat Operator. + + The deep cat operator is a modification of the deep-o-net architecture. Here the branch network is called input network, and the trunk network is called eval network. These changes were made to highlight their purpose. After the pass through both of these networks the outputs are stacked and passed through a thrid network called cat network. This has three advantages: 1) The operator does not need to learn basis functions without having access to the evaluation locations. Integrating the evaluation locations earliear in theory allows for a higher level of adaptive abstration. 2) The hyper-parameters can be thought of as a control mechanism, dictating the flow of information. This can be further escalated by tweaking the input_cat_ratio parameter. 3) This operator does not need to learn basis functions functions at all. The level of abstraction gained by the cat-network can be much higher, not relying on a single operation only. + + Args: + shapes: Operator shapes. + input_net_width: Width of the input net (deep residual network). Defaults to 32. + input_net_depth: Depth of the input net (deep residual network). Defaults to 4. + eval_net_width: Width of the eval net (deep residual network). Defaults to 32. + eval_net_depth: Depth of the eval net (deep residual network). Defaults to 4. + input_cat_ratio: Ratio indicating how many values of the concatenated tensor originates from the input net. Controls flow of information into input- and eval-net. Defaults to 0.5. + cat_net_width: Width of the cat net (deep residual network). Defaults to 32. + cat_net_depth: Depth of the cat net (deep residual network). Defaults to 4. + act: Activation function. Defaults to Tanh. + device: Device. + """ + + def __init__( + self, + shapes: OperatorShapes, + input_net_width: int = 32, + input_net_depth: int = 4, + eval_net_width: int = 32, + eval_net_depth: int = 4, + input_cat_ratio: float = 0.5, + cat_net_width: int = 32, + cat_net_depth: int = 4, + act: Optional[nn.Module] = None, + device: Optional[torch.device] = None, + ): + super().__init__(shapes=shapes, device=device) + + if act is None: + act = nn.Tanh() + + assert ( + 1.0 > input_cat_ratio > 0.0 + ), f"Ratio has to be in [0, 1], but found {input_cat_ratio}" + input_out_width = ceil(cat_net_width * input_cat_ratio) + assert ( + input_out_width != cat_net_width + ), f"Input cat ratio {input_cat_ratio} results in eval net width equal zero." + + input_in_width = prod(shapes.u.size) * shapes.u.dim + self.input_net = DeepResidualNetwork( + input_size=input_in_width, + output_size=input_out_width, + width=input_net_width, + depth=input_net_depth, + act=act, + device=device, + ) + + eval_out_width = cat_net_width - input_out_width + self.eval_net = DeepResidualNetwork( + input_size=shapes.y.dim, + output_size=eval_out_width, + width=eval_net_width, + depth=eval_net_depth, + act=act, + device=device, + ) + + self.cat_act = act # no activation before first and after last layer + + self.cat_net = DeepResidualNetwork( + input_size=cat_net_width, + output_size=shapes.v.dim, + width=cat_net_width, + depth=cat_net_depth, + act=act, + device=device, + ) + + def forward( + self, _: torch.Tensor, u: torch.Tensor, y: torch.Tensor + ) -> torch.Tensor: + """Forward pass through the operator. + + Args: + _: Tensor containing sensor locations. Ignored. + u: Tensor containing values of sensors. Of shape (batch_size, u_dim, num_sensors...). + y: Tensor containing evaluation locations. Of shape (batch_size, y_dim, num_evaluations...). + + Returns: + Tensor of predicted evaluation values. Of shape (batch_size, v_dim, num_evaluations...). + """ + ipt = torch.flatten(u, start_dim=1) + ipt = self.input_net(ipt) + + y_num = y.shape[2:] + eval = y.flatten(start_dim=2).transpose(1, -1) + eval = self.eval_net(eval) + + ipt = ipt.unsqueeze(1).expand(-1, eval.size(1), -1) + cat = torch.cat([ipt, eval], dim=-1) + out = self.cat_act(cat) + out = self.cat_net(out) + + return out.reshape(-1, self.shapes.v.dim, *y_num) From bfab017478098428e4129edf3187a69033ab7b04 Mon Sep 17 00:00:00 2001 From: Jakob Wagner Date: Thu, 25 Jul 2024 14:26:21 +0200 Subject: [PATCH 3/8] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20b83429..16ef24b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add `Attention` base class, `MultiHeadAttention`, and `ScaledDotProductAttention` classes. - Add `branch_network` and `trunk_network` arguments to `DeepONet` to allow for custom network architectures. - Add `MaskedOperator` base class. +- Add `DeepCatOperator`. ## 0.1.0 From d8feabb1e3d80e4d284ed2a7c50557c5419c55da Mon Sep 17 00:00:00 2001 From: JakobEliasWagner Date: Thu, 25 Jul 2024 14:34:31 +0200 Subject: [PATCH 4/8] update docstring --- src/continuiti/operators/deep_cat_operator.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/continuiti/operators/deep_cat_operator.py b/src/continuiti/operators/deep_cat_operator.py index af2abf00..21bffc32 100644 --- a/src/continuiti/operators/deep_cat_operator.py +++ b/src/continuiti/operators/deep_cat_operator.py @@ -15,7 +15,20 @@ class DeepCatOperator(Operator): """Deep Cat Operator. - The deep cat operator is a modification of the deep-o-net architecture. Here the branch network is called input network, and the trunk network is called eval network. These changes were made to highlight their purpose. After the pass through both of these networks the outputs are stacked and passed through a thrid network called cat network. This has three advantages: 1) The operator does not need to learn basis functions without having access to the evaluation locations. Integrating the evaluation locations earliear in theory allows for a higher level of adaptive abstration. 2) The hyper-parameters can be thought of as a control mechanism, dictating the flow of information. This can be further escalated by tweaking the input_cat_ratio parameter. 3) This operator does not need to learn basis functions functions at all. The level of abstraction gained by the cat-network can be much higher, not relying on a single operation only. + This class implements the DeepCatOperator, a neural operator inspired by the DeepONet. It consists of three main + parts: + 1. **Input Network**: Analogous to the "branch network," it processes the sensor inputs (`u`). + 2. **Eval Network**: Analogous to the "trunk network," it processes the evaluation locations (`y`). + 3. **Cat Network**: Combines the outputs from the Input and Eval Networks to produce the final output. + + The architecture offers three potential advantages: + 1. It allows the operator to integrate evaluation locations earlier, enabling a higher level of adaptive + abstraction. + 2. The hyperparameters can be thought of as a control mechanism, dictating the flow of information. The + `input_cat_ratio` hyperparameter provides a control mechanism for the information flow, allowing fine-tuning of + the contributions from the Input and Eval Networks. + 3. It can achieve a high level of abstraction without relying on learning basis functions, evaluated in a single + operation (dot product). Args: shapes: Operator shapes. @@ -23,7 +36,8 @@ class DeepCatOperator(Operator): input_net_depth: Depth of the input net (deep residual network). Defaults to 4. eval_net_width: Width of the eval net (deep residual network). Defaults to 32. eval_net_depth: Depth of the eval net (deep residual network). Defaults to 4. - input_cat_ratio: Ratio indicating how many values of the concatenated tensor originates from the input net. Controls flow of information into input- and eval-net. Defaults to 0.5. + input_cat_ratio: Ratio indicating how many values of the concatenated tensor originates from the input net. + Controls flow of information into input- and eval-net. Defaults to 0.5. cat_net_width: Width of the cat net (deep residual network). Defaults to 32. cat_net_depth: Depth of the cat net (deep residual network). Defaults to 4. act: Activation function. Defaults to Tanh. From 228802be4f1954b2334e2e343e3d1116c3a666c6 Mon Sep 17 00:00:00 2001 From: Jakob Wagner Date: Wed, 14 Aug 2024 14:25:03 +0200 Subject: [PATCH 5/8] add requested changes --- src/continuiti/operators/deep_cat_operator.py | 98 +++++++++++-------- tests/operators/test_deep_cat_operator.py | 4 +- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/continuiti/operators/deep_cat_operator.py b/src/continuiti/operators/deep_cat_operator.py index 21bffc32..5f3b2855 100644 --- a/src/continuiti/operators/deep_cat_operator.py +++ b/src/continuiti/operators/deep_cat_operator.py @@ -17,41 +17,57 @@ class DeepCatOperator(Operator): This class implements the DeepCatOperator, a neural operator inspired by the DeepONet. It consists of three main parts: - 1. **Input Network**: Analogous to the "branch network," it processes the sensor inputs (`u`). - 2. **Eval Network**: Analogous to the "trunk network," it processes the evaluation locations (`y`). - 3. **Cat Network**: Combines the outputs from the Input and Eval Networks to produce the final output. - - The architecture offers three potential advantages: - 1. It allows the operator to integrate evaluation locations earlier, enabling a higher level of adaptive - abstraction. - 2. The hyperparameters can be thought of as a control mechanism, dictating the flow of information. The - `input_cat_ratio` hyperparameter provides a control mechanism for the information flow, allowing fine-tuning of - the contributions from the Input and Eval Networks. - 3. It can achieve a high level of abstraction without relying on learning basis functions, evaluated in a single - operation (dot product). + 1. **Branch Network**: Processes the sensor inputs (`u`). + 2. **Trunk Network**: Processes the evaluation locations (`y`). + 3. **Cat Network**: Combines the outputs from the Branch- and Trunk-Network to produce the final output. + + This allows the operator to integrate evaluation locations earlier, while ensuring that both the sensor inputs and + the evaluation location contribute in a predictable form to the flow of information. Directly stacking both the + sensors and evaluation location can lead to an imbalance in the number of features in the neural operator. The + arg `branch_cat_ratio` dictates how this fraction is set (defaults to 50/50). The cat-network does not require the + neural operator to learn good basis functions. The information from the input space and the evaluation locations + can be taken into account early, allowing for better abstraction. + + ┌─────────────────────┐ ┌────────────────────┐ + │ *Branch Network* │ │ *Trunk Network* │ + │ Input (u) │ │ Input (y) │ + │ Output (b) │ │ Output (t) │ + └─────────────────┬───┘ └──┬─────────────────┘ + ┌─────────────────┴──────────┴─────────────────┐ + │ *Concatenation* │ + │ Input (b, t) │ + │ Output (c) │ + │ b.numel() / cat_net_width = branch_cat_ratio │ + └────────────────────┬─────────────────────────┘ + ┌────────┴─────────┐ + │ *Cat Network* │ + │ Input (c) │ + │ Output (v) │ + └──────────────────┘ Args: shapes: Operator shapes. - input_net_width: Width of the input net (deep residual network). Defaults to 32. - input_net_depth: Depth of the input net (deep residual network). Defaults to 4. - eval_net_width: Width of the eval net (deep residual network). Defaults to 32. - eval_net_depth: Depth of the eval net (deep residual network). Defaults to 4. - input_cat_ratio: Ratio indicating how many values of the concatenated tensor originates from the input net. - Controls flow of information into input- and eval-net. Defaults to 0.5. + branch_width: Width of the branch net (deep residual network). Defaults to 32. + branch_depth: Depth of the branch net (deep residual network). Defaults to 4. + trunk_width: Width of the trunk net (deep residual network). Defaults to 32. + trunk_depth: Depth of the trunk net (deep residual network). Defaults to 4. + branch_cat_ratio: Ratio indicating which fraction of the concatenated tensor originates from the branch net. + Controls flow of information into branch- and trunk-net. Defaults to 0.5. cat_net_width: Width of the cat net (deep residual network). Defaults to 32. cat_net_depth: Depth of the cat net (deep residual network). Defaults to 4. act: Activation function. Defaults to Tanh. device: Device. + """ def __init__( self, shapes: OperatorShapes, - input_net_width: int = 32, - input_net_depth: int = 4, - eval_net_width: int = 32, - eval_net_depth: int = 4, - input_cat_ratio: float = 0.5, + branch_width: int = 32, + branch_depth: int = 4, + trunk_width: int = 32, + trunk_depth: int = 4, + branch_cat_ratio: float = 0.5, cat_net_width: int = 32, cat_net_depth: int = 4, act: Optional[nn.Module] = None, @@ -63,29 +79,29 @@ def __init__( act = nn.Tanh() assert ( - 1.0 > input_cat_ratio > 0.0 - ), f"Ratio has to be in [0, 1], but found {input_cat_ratio}" - input_out_width = ceil(cat_net_width * input_cat_ratio) + 0.0 < branch_cat_ratio < 1.0 + ), f"Ratio has to be in [0, 1], but found {branch_cat_ratio}" + branch_out_width = ceil(cat_net_width * branch_cat_ratio) assert ( - input_out_width != cat_net_width - ), f"Input cat ratio {input_cat_ratio} results in eval net width equal zero." + branch_out_width != cat_net_width + ), f"Input cat ratio {branch_cat_ratio} results in eval net width equal zero." input_in_width = prod(shapes.u.size) * shapes.u.dim - self.input_net = DeepResidualNetwork( + self.branch_net = DeepResidualNetwork( input_size=input_in_width, - output_size=input_out_width, - width=input_net_width, - depth=input_net_depth, + output_size=branch_out_width, + width=branch_width, + depth=branch_depth, act=act, device=device, ) - eval_out_width = cat_net_width - input_out_width - self.eval_net = DeepResidualNetwork( + eval_out_width = cat_net_width - branch_out_width + self.trunk_net = DeepResidualNetwork( input_size=shapes.y.dim, output_size=eval_out_width, - width=eval_net_width, - depth=eval_net_depth, + width=trunk_width, + depth=trunk_depth, act=act, device=device, ) @@ -108,18 +124,18 @@ def forward( Args: _: Tensor containing sensor locations. Ignored. - u: Tensor containing values of sensors. Of shape (batch_size, u_dim, num_sensors...). - y: Tensor containing evaluation locations. Of shape (batch_size, y_dim, num_evaluations...). + u: Tensor containing values of sensors of shape (batch_size, u_dim, num_sensors...). + y: Tensor containing evaluation locations of shape (batch_size, y_dim, num_evaluations...). Returns: - Tensor of predicted evaluation values. Of shape (batch_size, v_dim, num_evaluations...). + Tensor of predicted evaluation values of shape (batch_size, v_dim, num_evaluations...). """ ipt = torch.flatten(u, start_dim=1) - ipt = self.input_net(ipt) + ipt = self.branch_net(ipt) y_num = y.shape[2:] eval = y.flatten(start_dim=2).transpose(1, -1) - eval = self.eval_net(eval) + eval = self.trunk_net(eval) ipt = ipt.unsqueeze(1).expand(-1, eval.size(1), -1) cat = torch.cat([ipt, eval], dim=-1) diff --git a/tests/operators/test_deep_cat_operator.py b/tests/operators/test_deep_cat_operator.py index 8f9db0bc..515c5864 100644 --- a/tests/operators/test_deep_cat_operator.py +++ b/tests/operators/test_deep_cat_operator.py @@ -26,8 +26,8 @@ def test_can_initialize(self, random_operator_dataset): def test_can_initialize_default_networks(self, random_operator_dataset): operator = DeepCatOperator(shapes=random_operator_dataset.shapes) - assert isinstance(operator.input_net, DeepResidualNetwork) - assert isinstance(operator.eval_net, DeepResidualNetwork) + assert isinstance(operator.branch_net, DeepResidualNetwork) + assert isinstance(operator.trunk_net, DeepResidualNetwork) assert isinstance(operator.cat_net, DeepResidualNetwork) def test_forward_shapes_correct(self, dcos, random_shape_operator_datasets): From ecf73d4deba5f5161552bdab186b9d5d815fb882 Mon Sep 17 00:00:00 2001 From: Samuel Burbulla Date: Mon, 19 Aug 2024 11:10:46 +0200 Subject: [PATCH 6/8] Minor refactoring. --- src/continuiti/operators/__init__.py | 2 +- .../{deep_cat_operator.py => dco.py} | 53 ++++++++++--------- ...{test_deep_cat_operator.py => test_dco.py} | 0 3 files changed, 30 insertions(+), 25 deletions(-) rename src/continuiti/operators/{deep_cat_operator.py => dco.py} (75%) rename tests/operators/{test_deep_cat_operator.py => test_dco.py} (100%) diff --git a/src/continuiti/operators/__init__.py b/src/continuiti/operators/__init__.py index 945e75d9..8d74b291 100644 --- a/src/continuiti/operators/__init__.py +++ b/src/continuiti/operators/__init__.py @@ -19,7 +19,7 @@ from .fno import FourierNeuralOperator from .shape import OperatorShapes from .cnn import ConvolutionalNeuralNetwork -from .deep_cat_operator import DeepCatOperator +from .dco import DeepCatOperator __all__ = [ "Operator", diff --git a/src/continuiti/operators/deep_cat_operator.py b/src/continuiti/operators/dco.py similarity index 75% rename from src/continuiti/operators/deep_cat_operator.py rename to src/continuiti/operators/dco.py index 5f3b2855..92868ccb 100644 --- a/src/continuiti/operators/deep_cat_operator.py +++ b/src/continuiti/operators/dco.py @@ -1,7 +1,7 @@ """ -`continuiti.operators.deep_cat_operator` +`continuiti.operators.dco` -The DeepCatOperator architecture. +The DeepCatOperator (DCO) architecture. """ import torch @@ -15,35 +15,40 @@ class DeepCatOperator(Operator): """Deep Cat Operator. - This class implements the DeepCatOperator, a neural operator inspired by the DeepONet. It consists of three main - parts: + This class implements the DeepCatOperator, a neural operator inspired by the DeepONet. + + It consists of three main parts: + 1. **Branch Network**: Processes the sensor inputs (`u`). 2. **Trunk Network**: Processes the evaluation locations (`y`). 3. **Cat Network**: Combines the outputs from the Branch- and Trunk-Network to produce the final output. + The architecture has the following structure: + + ┌─────────────────────┐ ┌────────────────────┐ + │ *Branch Network* │ │ *Trunk Network* │ + │ Input (u) │ │ Input (y) │ + │ Output (b) │ │ Output (t) │ + └─────────────────┬───┘ └──┬─────────────────┘ + ┌ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ┐ + │ *Concatenation* │ + │ Input (b, t) │ + │ Output (c) │ + │ branch_cat_ratio = b.numel() / cat_net_width │ + └ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ + ┌─────────┴────────┐ + │ *Cat Network* │ + │ Input (c) │ + │ Output (v) │ + └──────────────────┘ + + This allows the operator to integrate evaluation locations earlier, while ensuring that both the sensor inputs and the evaluation location contribute in a predictable form to the flow of information. Directly stacking both the sensors and evaluation location can lead to an imbalance in the number of features in the neural operator. The arg `branch_cat_ratio` dictates how this fraction is set (defaults to 50/50). The cat-network does not require the - neural operator to learn good basis functions. The information from the input space and the evaluation locations - can be taken into account early, allowing for better abstraction. - - ┌─────────────────────┐ ┌────────────────────┐ - │ *Branch Network* │ │ *Trunk Network* │ - │ Input (u) │ │ Input (y) │ - │ Output (b) │ │ Output (t) │ - └─────────────────┬───┘ └──┬─────────────────┘ - ┌─────────────────┴──────────┴─────────────────┐ - │ *Concatenation* │ - │ Input (b, t) │ - │ Output (c) │ - │ b.numel() / cat_net_width = branch_cat_ratio │ - └────────────────────┬─────────────────────────┘ - ┌────────┴─────────┐ - │ *Cat Network* │ - │ Input (c) │ - │ Output (v) │ - └──────────────────┘ + neural operator to learn good basis functions with the trunk network only. The information from the input space and + the evaluation locations can be taken into account early, allowing for better abstraction. Args: shapes: Operator shapes. @@ -80,7 +85,7 @@ def __init__( assert ( 0.0 < branch_cat_ratio < 1.0 - ), f"Ratio has to be in [0, 1], but found {branch_cat_ratio}" + ), f"Ratio has to be in (0, 1), but found {branch_cat_ratio}" branch_out_width = ceil(cat_net_width * branch_cat_ratio) assert ( branch_out_width != cat_net_width diff --git a/tests/operators/test_deep_cat_operator.py b/tests/operators/test_dco.py similarity index 100% rename from tests/operators/test_deep_cat_operator.py rename to tests/operators/test_dco.py From d73ae31549e29c6a415da618fda2a2544773e459 Mon Sep 17 00:00:00 2001 From: Samuel Burbulla Date: Mon, 19 Aug 2024 11:24:32 +0200 Subject: [PATCH 7/8] Disable lfs in test workflow. --- .github/workflows/test.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f426805c..a7ef17da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - lfs: "true" - name: Setup Python uses: actions/setup-python@v5 with: @@ -52,8 +50,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - lfs: "true" - name: Setup Python uses: actions/setup-python@v5 with: @@ -77,8 +73,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - lfs: "true" - name: Setup Python uses: actions/setup-python@v5 with: From b7c0b1083cc848446c2e300578f3953bd04f136b Mon Sep 17 00:00:00 2001 From: Samuel Burbulla Date: Mon, 19 Aug 2024 12:14:21 +0200 Subject: [PATCH 8/8] Add fallback for CI in meshes notebook. --- examples/meshes.ipynb | 523 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 498 insertions(+), 25 deletions(-) diff --git a/examples/meshes.ipynb b/examples/meshes.ipynb index 284f2e35..72ecbbca 100644 --- a/examples/meshes.ipynb +++ b/examples/meshes.ipynb @@ -51,12 +51,467 @@ "hide" ] }, + "outputs": [], + "source": [ + "torch.manual_seed(0)\n", + "plt.rcParams[\"axes.facecolor\"] = (1, 1, 1, 0)\n", + "plt.rcParams[\"figure.facecolor\"] = (1, 1, 1, 0)\n", + "plt.rcParams[\"legend.framealpha\"] = 0.0" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [ + "hide", + "skip-execution" + ] + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Info : Running 'gmsh -2 mediterranean.geo\n" + "Info : Running '/Users/samuelburbulla/code/continuiti/venv/bin/gmsh -2 /Users/samuelburbulla/code/continuiti/examples/../data/meshes/mediterranean.geo' [Gmsh 4.12.2, 1 node, max. 1 thread]\n", + "Info : Started on Mon Aug 19 12:07:12 2024\n", + "Info : Reading '/Users/samuelburbulla/code/continuiti/examples/../data/meshes/mediterranean.geo'...\n", + "Info : Done reading '/Users/samuelburbulla/code/continuiti/examples/../data/meshes/mediterranean.geo'\n", + "Info : Meshing 1D...\n", + "Info : [ 0%] Meshing curve 2 (Nurb)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Warning : Unknown curve 1\n", + "Warning : Unknown curve 5\n", + "Warning : Unknown curve 6\n", + "Warning : Unknown curve 7\n", + "Warning : Unknown curve 22\n", + "Warning : Unknown curve 158\n", + "Warning : Unknown curve 180\n", + "Warning : Unknown curve 191\n", + "Warning : Unknown curve 235\n", + "Warning : Unknown curve 241\n", + "Warning : Unknown curve 486\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Info : [ 10%] Meshing curve 3 (Nurb)\n", + "Info : [ 10%] Meshing curve 4 (Nurb)\n", + "Info : [ 10%] Meshing curve 8 (Nurb)\n", + "Info : [ 10%] Meshing curve 9 (Nurb)\n", + "Info : [ 10%] Meshing curve 10 (Nurb)\n", + "Info : [ 10%] Meshing curve 11 (Nurb)\n", + "Info : [ 10%] Meshing curve 12 (Nurb)\n", + "Info : [ 10%] Meshing curve 13 (Nurb)\n", + "Info : [ 10%] Meshing curve 14 (Nurb)\n", + "Info : [ 10%] Meshing curve 15 (Nurb)\n", + "Info : [ 10%] Meshing curve 16 (Nurb)\n", + "Info : [ 10%] Meshing curve 17 (Nurb)\n", + "Info : [ 10%] Meshing curve 18 (Nurb)\n", + "Info : [ 10%] Meshing curve 19 (Nurb)\n", + "Info : [ 10%] Meshing curve 20 (Nurb)\n", + "Info : [ 10%] Meshing curve 21 (Nurb)\n", + "Info : [ 10%] Meshing curve 23 (Nurb)\n", + "Info : [ 10%] Meshing curve 24 (Nurb)\n", + "Info : [ 10%] Meshing curve 25 (Nurb)\n", + "Info : [ 10%] Meshing curve 26 (Nurb)\n", + "Info : [ 10%] Meshing curve 27 (Nurb)\n", + "Info : [ 10%] Meshing curve 28 (Nurb)\n", + "Info : [ 10%] Meshing curve 29 (Nurb)\n", + "Info : [ 10%] Meshing curve 30 (Nurb)\n", + "Info : [ 10%] Meshing curve 31 (Nurb)\n", + "Info : [ 10%] Meshing curve 32 (Nurb)\n", + "Info : [ 10%] Meshing curve 33 (Nurb)\n", + "Info : [ 10%] Meshing curve 34 (Nurb)\n", + "Info : [ 10%] Meshing curve 35 (Nurb)\n", + "Info : [ 10%] Meshing curve 36 (Nurb)\n", + "Info : [ 10%] Meshing curve 37 (Nurb)\n", + "Info : [ 10%] Meshing curve 38 (Nurb)\n", + "Info : [ 10%] Meshing curve 39 (Nurb)\n", + "Info : [ 10%] Meshing curve 40 (Nurb)\n", + "Info : [ 10%] Meshing curve 41 (Nurb)\n", + "Info : [ 10%] Meshing curve 42 (Nurb)\n", + "Info : [ 10%] Meshing curve 43 (Nurb)\n", + "Info : [ 20%] Meshing curve 44 (Nurb)\n", + "Info : [ 20%] Meshing curve 45 (Nurb)\n", + "Info : [ 20%] Meshing curve 46 (Nurb)\n", + "Info : [ 20%] Meshing curve 47 (Nurb)\n", + "Info : [ 20%] Meshing curve 48 (Nurb)\n", + "Info : [ 20%] Meshing curve 49 (Nurb)\n", + "Info : [ 20%] Meshing curve 50 (Nurb)\n", + "Info : [ 20%] Meshing curve 51 (Nurb)\n", + "Info : [ 20%] Meshing curve 52 (Nurb)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Warning : Could not converge parametrisation of (4.58018e+06,2.23382e+06,3.82384e+06) on curve 51, taking parameter with lowest error\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Info : [ 20%] Meshing curve 53 (Nurb)\n", + "Info : [ 20%] Meshing curve 54 (Nurb)\n", + "Info : [ 20%] Meshing curve 55 (Nurb)\n", + "Info : [ 20%] Meshing curve 56 (Nurb)\n", + "Info : [ 20%] Meshing curve 57 (Nurb)\n", + "Info : [ 20%] Meshing curve 58 (Nurb)\n", + "Info : [ 20%] Meshing curve 59 (Nurb)\n", + "Info : [ 20%] Meshing curve 60 (Nurb)\n", + "Info : [ 20%] Meshing curve 61 (Nurb)\n", + "Info : [ 20%] Meshing curve 62 (Nurb)\n", + "Info : [ 20%] Meshing curve 63 (Nurb)\n", + "Info : [ 20%] Meshing curve 64 (Nurb)\n", + "Info : [ 20%] Meshing curve 65 (Nurb)\n", + "Info : [ 20%] Meshing curve 66 (Nurb)\n", + "Info : [ 20%] Meshing curve 67 (Nurb)\n", + "Info : [ 20%] Meshing curve 68 (Nurb)\n", + "Info : [ 20%] Meshing curve 69 (Nurb)\n", + "Info : [ 20%] Meshing curve 70 (Nurb)\n", + "Info : [ 20%] Meshing curve 71 (Nurb)\n", + "Info : [ 20%] Meshing curve 72 (Nurb)\n", + "Info : [ 20%] Meshing curve 73 (Nurb)\n", + "Info : [ 20%] Meshing curve 74 (Nurb)\n", + "Info : [ 20%] Meshing curve 75 (Nurb)\n", + "Info : [ 20%] Meshing curve 76 (Nurb)\n", + "Info : [ 20%] Meshing curve 77 (Nurb)\n", + "Info : [ 20%] Meshing curve 78 (Nurb)\n", + "Info : [ 20%] Meshing curve 79 (Nurb)\n", + "Info : [ 20%] Meshing curve 80 (Nurb)\n", + "Info : [ 20%] Meshing curve 81 (Nurb)\n", + "Info : [ 30%] Meshing curve 82 (Nurb)\n", + "Info : [ 30%] Meshing curve 83 (Nurb)\n", + "Info : [ 30%] Meshing curve 84 (Nurb)\n", + "Info : [ 30%] Meshing curve 85 (Nurb)\n", + "Info : [ 30%] Meshing curve 86 (Nurb)\n", + "Info : [ 30%] Meshing curve 87 (Nurb)\n", + "Info : [ 30%] Meshing curve 88 (Nurb)\n", + "Info : [ 30%] Meshing curve 89 (Nurb)\n", + "Info : [ 30%] Meshing curve 90 (Nurb)\n", + "Info : [ 30%] Meshing curve 91 (Nurb)\n", + "Info : [ 30%] Meshing curve 92 (Nurb)\n", + "Info : [ 30%] Meshing curve 93 (Nurb)\n", + "Info : [ 30%] Meshing curve 94 (Nurb)\n", + "Info : [ 30%] Meshing curve 95 (Nurb)\n", + "Info : [ 30%] Meshing curve 96 (Nurb)\n", + "Info : [ 30%] Meshing curve 97 (Nurb)\n", + "Info : [ 30%] Meshing curve 98 (Nurb)\n", + "Info : [ 30%] Meshing curve 99 (Nurb)\n", + "Info : [ 30%] Meshing curve 100 (Nurb)\n", + "Info : [ 30%] Meshing curve 101 (Nurb)\n", + "Info : [ 30%] Meshing curve 102 (Nurb)\n", + "Info : [ 30%] Meshing curve 103 (Nurb)\n", + "Info : [ 30%] Meshing curve 105 (Nurb)\n", + "Info : [ 30%] Meshing curve 106 (Nurb)\n", + "Info : [ 30%] Meshing curve 107 (Nurb)\n", + "Info : [ 30%] Meshing curve 108 (Nurb)\n", + "Info : [ 30%] Meshing curve 109 (Nurb)\n", + "Info : [ 30%] Meshing curve 110 (Nurb)\n", + "Info : [ 30%] Meshing curve 111 (Nurb)\n", + "Info : [ 30%] Meshing curve 112 (Nurb)\n", + "Info : [ 30%] Meshing curve 113 (Nurb)\n", + "Info : [ 30%] Meshing curve 115 (Nurb)\n", + "Info : [ 30%] Meshing curve 117 (Nurb)\n", + "Info : [ 30%] Meshing curve 118 (Nurb)\n", + "Info : [ 30%] Meshing curve 119 (Nurb)\n", + "Info : [ 30%] Meshing curve 120 (Nurb)\n", + "Info : [ 30%] Meshing curve 121 (Nurb)\n", + "Info : [ 40%] Meshing curve 122 (Nurb)\n", + "Info : [ 40%] Meshing curve 123 (Nurb)\n", + "Info : [ 40%] Meshing curve 124 (Nurb)\n", + "Info : [ 40%] Meshing curve 125 (Nurb)\n", + "Info : [ 40%] Meshing curve 126 (Nurb)\n", + "Info : [ 40%] Meshing curve 127 (Nurb)\n", + "Info : [ 40%] Meshing curve 128 (Nurb)\n", + "Info : [ 40%] Meshing curve 129 (Nurb)\n", + "Info : [ 40%] Meshing curve 130 (Nurb)\n", + "Info : [ 40%] Meshing curve 131 (Nurb)\n", + "Info : [ 40%] Meshing curve 133 (Nurb)\n", + "Info : [ 40%] Meshing curve 134 (Nurb)\n", + "Info : [ 40%] Meshing curve 135 (Nurb)\n", + "Info : [ 40%] Meshing curve 136 (Nurb)\n", + "Info : [ 40%] Meshing curve 137 (Nurb)\n", + "Info : [ 40%] Meshing curve 138 (Nurb)\n", + "Info : [ 40%] Meshing curve 139 (Nurb)\n", + "Info : [ 40%] Meshing curve 140 (Nurb)\n", + "Info : [ 40%] Meshing curve 141 (Nurb)\n", + "Info : [ 40%] Meshing curve 142 (Nurb)\n", + "Info : [ 40%] Meshing curve 143 (Nurb)\n", + "Info : [ 40%] Meshing curve 144 (Nurb)\n", + "Info : [ 40%] Meshing curve 145 (Nurb)\n", + "Info : [ 40%] Meshing curve 146 (Nurb)\n", + "Info : [ 40%] Meshing curve 147 (Nurb)\n", + "Info : [ 40%] Meshing curve 148 (Nurb)\n", + "Info : [ 40%] Meshing curve 149 (Nurb)\n", + "Info : [ 40%] Meshing curve 151 (Nurb)\n", + "Info : [ 40%] Meshing curve 152 (Nurb)\n", + "Info : [ 40%] Meshing curve 153 (Nurb)\n", + "Info : [ 40%] Meshing curve 154 (Nurb)\n", + "Info : [ 40%] Meshing curve 155 (Nurb)\n", + "Info : [ 40%] Meshing curve 156 (Nurb)\n", + "Info : [ 40%] Meshing curve 157 (Nurb)\n", + "Info : [ 40%] Meshing curve 159 (Nurb)\n", + "Info : [ 40%] Meshing curve 160 (Nurb)\n", + "Info : [ 40%] Meshing curve 161 (Nurb)\n", + "Info : [ 40%] Meshing curve 162 (Nurb)\n", + "Info : [ 50%] Meshing curve 163 (Nurb)\n", + "Info : [ 50%] Meshing curve 164 (Nurb)\n", + "Info : [ 50%] Meshing curve 165 (Nurb)\n", + "Info : [ 50%] Meshing curve 166 (Nurb)\n", + "Info : [ 50%] Meshing curve 167 (Nurb)\n", + "Info : [ 50%] Meshing curve 168 (Nurb)\n", + "Info : [ 50%] Meshing curve 169 (Nurb)\n", + "Info : [ 50%] Meshing curve 170 (Nurb)\n", + "Info : [ 50%] Meshing curve 171 (Nurb)\n", + "Info : [ 50%] Meshing curve 172 (Nurb)\n", + "Info : [ 50%] Meshing curve 173 (Nurb)\n", + "Info : [ 50%] Meshing curve 174 (Nurb)\n", + "Info : [ 50%] Meshing curve 175 (Nurb)\n", + "Info : [ 50%] Meshing curve 176 (Nurb)\n", + "Info : [ 50%] Meshing curve 177 (Nurb)\n", + "Info : [ 50%] Meshing curve 178 (Nurb)\n", + "Info : [ 50%] Meshing curve 179 (Nurb)\n", + "Info : [ 50%] Meshing curve 181 (Nurb)\n", + "Info : [ 50%] Meshing curve 182 (Nurb)\n", + "Info : [ 50%] Meshing curve 183 (Nurb)\n", + "Info : [ 50%] Meshing curve 184 (Nurb)\n", + "Info : [ 50%] Meshing curve 185 (Nurb)\n", + "Info : [ 50%] Meshing curve 186 (Nurb)\n", + "Info : [ 50%] Meshing curve 187 (Nurb)\n", + "Info : [ 50%] Meshing curve 188 (Nurb)\n", + "Info : [ 50%] Meshing curve 189 (Nurb)\n", + "Info : [ 50%] Meshing curve 190 (Nurb)\n", + "Info : [ 50%] Meshing curve 192 (Nurb)\n", + "Info : [ 50%] Meshing curve 193 (Nurb)\n", + "Info : [ 50%] Meshing curve 194 (Nurb)\n", + "Info : [ 50%] Meshing curve 195 (Nurb)\n", + "Info : [ 50%] Meshing curve 196 (Nurb)\n", + "Info : [ 50%] Meshing curve 197 (Nurb)\n", + "Info : [ 50%] Meshing curve 198 (Nurb)\n", + "Info : [ 50%] Meshing curve 199 (Nurb)\n", + "Info : [ 50%] Meshing curve 200 (Nurb)\n", + "Info : [ 50%] Meshing curve 202 (Nurb)\n", + "Info : [ 60%] Meshing curve 204 (Nurb)\n", + "Info : [ 60%] Meshing curve 205 (Nurb)\n", + "Info : [ 60%] Meshing curve 206 (Nurb)\n", + "Info : [ 60%] Meshing curve 207 (Nurb)\n", + "Info : [ 60%] Meshing curve 208 (Nurb)\n", + "Info : [ 60%] Meshing curve 209 (Nurb)\n", + "Info : [ 60%] Meshing curve 211 (Nurb)\n", + "Info : [ 60%] Meshing curve 212 (Nurb)\n", + "Info : [ 60%] Meshing curve 213 (Nurb)\n", + "Info : [ 60%] Meshing curve 214 (Nurb)\n", + "Info : [ 60%] Meshing curve 215 (Nurb)\n", + "Info : [ 60%] Meshing curve 216 (Nurb)\n", + "Info : [ 60%] Meshing curve 217 (Nurb)\n", + "Info : [ 60%] Meshing curve 219 (Nurb)\n", + "Info : [ 60%] Meshing curve 220 (Nurb)\n", + "Info : [ 60%] Meshing curve 222 (Nurb)\n", + "Info : [ 60%] Meshing curve 223 (Nurb)\n", + "Info : [ 60%] Meshing curve 224 (Nurb)\n", + "Info : [ 60%] Meshing curve 225 (Nurb)\n", + "Info : [ 60%] Meshing curve 226 (Nurb)\n", + "Info : [ 60%] Meshing curve 227 (Nurb)\n", + "Info : [ 60%] Meshing curve 228 (Nurb)\n", + "Info : [ 60%] Meshing curve 229 (Nurb)\n", + "Info : [ 60%] Meshing curve 230 (Nurb)\n", + "Info : [ 60%] Meshing curve 231 (Nurb)\n", + "Info : [ 60%] Meshing curve 232 (Nurb)\n", + "Info : [ 60%] Meshing curve 233 (Nurb)\n", + "Info : [ 60%] Meshing curve 236 (Nurb)\n", + "Info : [ 60%] Meshing curve 237 (Nurb)\n", + "Info : [ 60%] Meshing curve 238 (Nurb)\n", + "Info : [ 60%] Meshing curve 239 (Nurb)\n", + "Info : [ 60%] Meshing curve 240 (Nurb)\n", + "Info : [ 60%] Meshing curve 242 (Nurb)\n", + "Info : [ 60%] Meshing curve 243 (Nurb)\n", + "Info : [ 60%] Meshing curve 244 (Nurb)\n", + "Info : [ 60%] Meshing curve 245 (Nurb)\n", + "Info : [ 60%] Meshing curve 246 (Nurb)\n", + "Info : [ 60%] Meshing curve 247 (Nurb)\n", + "Info : [ 70%] Meshing curve 248 (Nurb)\n", + "Info : [ 70%] Meshing curve 249 (Nurb)\n", + "Info : [ 70%] Meshing curve 250 (Nurb)\n", + "Info : [ 70%] Meshing curve 251 (Nurb)\n", + "Info : [ 70%] Meshing curve 252 (Nurb)\n", + "Info : [ 70%] Meshing curve 253 (Nurb)\n", + "Info : [ 70%] Meshing curve 254 (Nurb)\n", + "Info : [ 70%] Meshing curve 255 (Nurb)\n", + "Info : [ 70%] Meshing curve 256 (Nurb)\n", + "Info : [ 70%] Meshing curve 257 (Nurb)\n", + "Info : [ 70%] Meshing curve 258 (Nurb)\n", + "Info : [ 70%] Meshing curve 259 (Nurb)\n", + "Info : [ 70%] Meshing curve 260 (Nurb)\n", + "Info : [ 70%] Meshing curve 261 (Nurb)\n", + "Info : [ 70%] Meshing curve 262 (Nurb)\n", + "Info : [ 70%] Meshing curve 263 (Nurb)\n", + "Info : [ 70%] Meshing curve 264 (Nurb)\n", + "Info : [ 70%] Meshing curve 265 (Nurb)\n", + "Info : [ 70%] Meshing curve 266 (Nurb)\n", + "Info : [ 70%] Meshing curve 269 (Nurb)\n", + "Info : [ 70%] Meshing curve 270 (Nurb)\n", + "Info : [ 70%] Meshing curve 271 (Nurb)\n", + "Info : [ 70%] Meshing curve 272 (Nurb)\n", + "Info : [ 70%] Meshing curve 273 (Nurb)\n", + "Info : [ 70%] Meshing curve 274 (Nurb)\n", + "Info : [ 70%] Meshing curve 275 (Nurb)\n", + "Info : [ 70%] Meshing curve 277 (Nurb)\n", + "Info : [ 70%] Meshing curve 278 (Nurb)\n", + "Info : [ 70%] Meshing curve 280 (Nurb)\n", + "Info : [ 70%] Meshing curve 281 (Nurb)\n", + "Info : [ 70%] Meshing curve 282 (Nurb)\n", + "Info : [ 70%] Meshing curve 283 (Nurb)\n", + "Info : [ 70%] Meshing curve 284 (Nurb)\n", + "Info : [ 70%] Meshing curve 285 (Nurb)\n", + "Info : [ 70%] Meshing curve 286 (Nurb)\n", + "Info : [ 70%] Meshing curve 287 (Nurb)\n", + "Info : [ 70%] Meshing curve 288 (Nurb)\n", + "Info : [ 80%] Meshing curve 289 (Nurb)\n", + "Info : [ 80%] Meshing curve 290 (Nurb)\n", + "Info : [ 80%] Meshing curve 291 (Nurb)\n", + "Info : [ 80%] Meshing curve 292 (Nurb)\n", + "Info : [ 80%] Meshing curve 293 (Nurb)\n", + "Info : [ 80%] Meshing curve 294 (Nurb)\n", + "Info : [ 80%] Meshing curve 295 (Nurb)\n", + "Info : [ 80%] Meshing curve 296 (Nurb)\n", + "Info : [ 80%] Meshing curve 297 (Nurb)\n", + "Info : [ 80%] Meshing curve 298 (Nurb)\n", + "Info : [ 80%] Meshing curve 299 (Nurb)\n", + "Info : [ 80%] Meshing curve 300 (Nurb)\n", + "Info : [ 80%] Meshing curve 301 (Nurb)\n", + "Info : [ 80%] Meshing curve 302 (Nurb)\n", + "Info : [ 80%] Meshing curve 303 (Nurb)\n", + "Info : [ 80%] Meshing curve 304 (Nurb)\n", + "Info : [ 80%] Meshing curve 306 (Nurb)\n", + "Info : [ 80%] Meshing curve 307 (Nurb)\n", + "Info : [ 80%] Meshing curve 308 (Nurb)\n", + "Info : [ 80%] Meshing curve 309 (Nurb)\n", + "Info : [ 80%] Meshing curve 310 (Nurb)\n", + "Info : [ 80%] Meshing curve 311 (Nurb)\n", + "Info : [ 80%] Meshing curve 312 (Nurb)\n", + "Info : [ 80%] Meshing curve 313 (Nurb)\n", + "Info : [ 80%] Meshing curve 314 (Nurb)\n", + "Info : [ 80%] Meshing curve 315 (Nurb)\n", + "Info : [ 80%] Meshing curve 316 (Nurb)\n", + "Info : [ 80%] Meshing curve 317 (Nurb)\n", + "Info : [ 80%] Meshing curve 318 (Nurb)\n", + "Info : [ 80%] Meshing curve 319 (Nurb)\n", + "Info : [ 80%] Meshing curve 320 (Nurb)\n", + "Info : [ 80%] Meshing curve 321 (Nurb)\n", + "Info : [ 80%] Meshing curve 322 (Nurb)\n", + "Info : [ 80%] Meshing curve 323 (Nurb)\n", + "Info : [ 80%] Meshing curve 324 (Nurb)\n", + "Info : [ 80%] Meshing curve 325 (Nurb)\n", + "Info : [ 80%] Meshing curve 326 (Nurb)\n", + "Info : [ 80%] Meshing curve 327 (Nurb)\n", + "Info : [ 90%] Meshing curve 329 (Nurb)\n", + "Info : [ 90%] Meshing curve 330 (Nurb)\n", + "Info : [ 90%] Meshing curve 331 (Nurb)\n", + "Info : [ 90%] Meshing curve 332 (Nurb)\n", + "Info : [ 90%] Meshing curve 334 (Nurb)\n", + "Info : [ 90%] Meshing curve 335 (Nurb)\n", + "Info : [ 90%] Meshing curve 339 (Nurb)\n", + "Info : [ 90%] Meshing curve 341 (Nurb)\n", + "Info : [ 90%] Meshing curve 342 (Nurb)\n", + "Info : [ 90%] Meshing curve 343 (Nurb)\n", + "Info : [ 90%] Meshing curve 347 (Nurb)\n", + "Info : [ 90%] Meshing curve 348 (Nurb)\n", + "Info : [ 90%] Meshing curve 352 (Nurb)\n", + "Info : [ 90%] Meshing curve 353 (Nurb)\n", + "Info : [ 90%] Meshing curve 355 (Nurb)\n", + "Info : [ 90%] Meshing curve 356 (Nurb)\n", + "Info : [ 90%] Meshing curve 357 (Nurb)\n", + "Info : [ 90%] Meshing curve 359 (Nurb)\n", + "Info : [ 90%] Meshing curve 365 (Nurb)\n", + "Info : [ 90%] Meshing curve 366 (Nurb)\n", + "Info : [ 90%] Meshing curve 372 (Nurb)\n", + "Info : [ 90%] Meshing curve 373 (Nurb)\n", + "Info : [ 90%] Meshing curve 374 (Nurb)\n", + "Info : [ 90%] Meshing curve 377 (Nurb)\n", + "Info : [ 90%] Meshing curve 378 (Nurb)\n", + "Info : [ 90%] Meshing curve 383 (Nurb)\n", + "Info : [ 90%] Meshing curve 385 (Nurb)\n", + "Info : [ 90%] Meshing curve 389 (Nurb)\n", + "Info : [ 90%] Meshing curve 390 (Nurb)\n", + "Info : [ 90%] Meshing curve 391 (Nurb)\n", + "Info : [ 90%] Meshing curve 392 (Nurb)\n", + "Info : [ 90%] Meshing curve 396 (Nurb)\n", + "Info : [ 90%] Meshing curve 397 (Nurb)\n", + "Info : [ 90%] Meshing curve 400 (Nurb)\n", + "Info : [ 90%] Meshing curve 401 (Nurb)\n", + "Info : [ 90%] Meshing curve 405 (Nurb)\n", + "Info : [ 90%] Meshing curve 406 (Nurb)\n", + "Info : [100%] Meshing curve 410 (Nurb)\n", + "Info : [100%] Meshing curve 411 (Nurb)\n", + "Info : [100%] Meshing curve 412 (Nurb)\n", + "Info : [100%] Meshing curve 415 (Nurb)\n", + "Info : [100%] Meshing curve 418 (Nurb)\n", + "Info : [100%] Meshing curve 419 (Nurb)\n", + "Info : [100%] Meshing curve 422 (Nurb)\n", + "Info : [100%] Meshing curve 426 (Nurb)\n", + "Info : [100%] Meshing curve 427 (Nurb)\n", + "Info : [100%] Meshing curve 430 (Nurb)\n", + "Info : [100%] Meshing curve 433 (Nurb)\n", + "Info : [100%] Meshing curve 436 (Nurb)\n", + "Info : [100%] Meshing curve 437 (Nurb)\n", + "Info : [100%] Meshing curve 440 (Nurb)\n", + "Info : [100%] Meshing curve 441 (Nurb)\n", + "Info : [100%] Meshing curve 445 (Nurb)\n", + "Info : [100%] Meshing curve 451 (Nurb)\n", + "Info : [100%] Meshing curve 457 (Nurb)\n", + "Info : [100%] Meshing curve 458 (Nurb)\n", + "Info : [100%] Meshing curve 459 (Line)\n", + "Info : [100%] Meshing curve 463 (Nurb)\n", + "Info : [100%] Meshing curve 464 (Nurb)\n", + "Info : [100%] Meshing curve 466 (Nurb)\n", + "Info : [100%] Meshing curve 467 (Nurb)\n", + "Info : [100%] Meshing curve 469 (Nurb)\n", + "Info : [100%] Meshing curve 470 (Nurb)\n", + "Info : [100%] Meshing curve 471 (Nurb)\n", + "Info : [100%] Meshing curve 473 (Nurb)\n", + "Info : [100%] Meshing curve 474 (Line)\n", + "Info : [100%] Meshing curve 475 (Nurb)\n", + "Info : [100%] Meshing curve 477 (Nurb)\n", + "Info : [100%] Meshing curve 478 (Nurb)\n", + "Info : [100%] Meshing curve 480 (Nurb)\n", + "Info : [100%] Meshing curve 482 (Nurb)\n", + "Info : [100%] Meshing curve 483 (Nurb)\n", + "Info : [100%] Meshing curve 484 (Nurb)\n", + "Info : [100%] Meshing curve 485 (Nurb)\n", + "Info : Done meshing 1D (Wall 20.3164s, CPU 19.753s)\n", + "Info : Meshing 2D...\n", + "Info : Meshing surface 487 (Parametric surface, Frontal-Delaunay)\n", + "Info : :-( There are 2 intersections in the 1D mesh (curves 225 457)\n", + "Info : 8-| Splitting those edges and trying again\n", + "Info : :-) All edges recovered after 1 iteration\n", + "Info : Done meshing 2D (Wall 0.510899s, CPU 0.496827s)\n", + "Info : 23486 nodes 38449 elements\n", + "Info : Writing '/Users/samuelburbulla/code/continuiti/examples/../data/meshes/mediterranean.msh'...\n", + "Info : Done writing '/Users/samuelburbulla/code/continuiti/examples/../data/meshes/mediterranean.msh'\n", + "Info : Stopped on Mon Aug 19 12:07:33 2024 (From start: Wall 21.1754s, CPU 21.7172s)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Warning : ------------------------------\n", + "Warning : Mesh generation error summary\n", + "Warning : 12 warnings\n", + "Warning : 0 errors\n", + "Warning : Check the full log for details\n", + "Warning : ------------------------------\n" ] }, { @@ -65,17 +520,12 @@ "0" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "torch.manual_seed(0)\n", - "plt.rcParams[\"axes.facecolor\"] = (1, 1, 1, 0)\n", - "plt.rcParams[\"figure.facecolor\"] = (1, 1, 1, 0)\n", - "plt.rcParams[\"legend.framealpha\"] = 0.0\n", - "\n", "# Generate the mesh\n", "import os\n", "meshes_dir = pathlib.Path.cwd().joinpath(\"..\", \"data\", \"meshes\")\n", @@ -92,10 +542,11 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": { "tags": [ - "invertible-output" + "invertible-output", + "skip-execution" ] }, "outputs": [], @@ -114,7 +565,24 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "meshes_dir = pathlib.Path.cwd().joinpath(\"..\", \"data\", \"meshes\")\n", + "gmsh_file = meshes_dir.joinpath(\"mediterranean.msh\")\n", + "\n", + "if not gmsh_file.is_file():\n", + " vertices = torch.rand(2, 100)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, "metadata": { "tags": [ "invertible-output", @@ -155,7 +623,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -186,7 +654,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": { "tags": [ "invertible-output", @@ -230,7 +698,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -251,7 +719,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -285,7 +753,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "metadata": { "tags": [ "hide-output" @@ -297,17 +765,17 @@ "output_type": "stream", "text": [ "Parameters: 5296 Device: mps\n", - "Epoch 3582/10000 Step 1/1 [====================] 27ms/step [1:35min<2:51min] - loss/train = 9.9980e-03 - stopping criterion met\n", + "Epoch 3582/10000 Step 1/1 [====================] 39ms/step [2:17min<4:07min] - loss/train = 9.9979e-03 - stopping criterion met\n", "\n" ] }, { "data": { "text/plain": [ - "Logs(epoch=3582, step=1, loss_train=0.009997953660786152, loss_test=None)" + "Logs(epoch=3582, step=1, loss_train=0.009997924789786339, loss_test=None)" ] }, - "execution_count": 10, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -328,7 +796,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 13, "metadata": { "tags": [ "invertible-output" @@ -339,7 +807,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "loss/test = 8.9704e-03\n" + "loss/test = 8.9705e-03\n" ] } ], @@ -362,8 +830,12 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": 14, + "metadata": { + "tags": [ + "skip-execution" + ] + }, "outputs": [], "source": [ "tri = Triangulation(vertices[0], vertices[1], mesh.get_cells())" @@ -371,17 +843,18 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 15, "metadata": { "tags": [ "invertible-output", - "hide-input" + "hide-input", + "skip-execution" ] }, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ]