-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussion for redesign #159
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, this looks already very good, only the definition of the Problem is IMO too specific. In addition, I would put the virtual sensors in a class (Postprocessing?) that is one the one hand storing/measuring the data, and then also able to visualize them.
class DirichletBCDefinition: | ||
""" | ||
Definition of a time- and position-dependent Dirichlet boundary condition. | ||
Note that functions cannot be serialized to JSON! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using the eval function, you could define a string and then convert that to a python function. The string can be stored in json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do that. To me, it screams security risk but then again, so would be importing any code you haven't written yourself
value: conlist(float, min_length=1, max_length=3) | Callable[[np.ndarray, float], np.ndarray] | ||
|
||
@dataclass(config=dict(arbitrary_types_allowed=True)) | ||
class BodyForceDefinition: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
body force is very much mechanics driven, for the heat equation,this would be different (volumetric heat flux). I guess this would also just be a function of x and t and returns the corresponding term.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I wasn't sure what to do here. What would be the general term for body force? So any integral
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
source term?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure how we should generalize this. As far as I see it, the body-force definition is constant because gravity is constant. But how is it with heat flux? From this doc https://www.simscale.com/docs/simulation-setup/boundary-conditions/volume-heat-flux/ I get that it could vary with time and position
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't that be the same as the functional representation of boundary conditions being a function of space and time? So there could certainly be constant or linear functions as standard (both relevant for body forces or linearly increasing boundary conditions) and then have the functional representation as additional option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but my question is more related to what should be allowed. In an ideal world, the data structure would ensure that only valid values (like constant gravity) are allowed, but I guess that should then be the responsibility of the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I guess it will be very hard to be able to define problems and test the validity beforehand (just from the syntax). And for Dirichlet BC I see now way around that, otherwise we are very limited, so then it makes no difference it that is added here again. But we could define two functions, one for BodyForce = constant and SpaceTimeDependentBodyForce(x,t).
dirichlet_bcs: list[DirichletBCDefinition] | None | ||
neumann_bcs: list[NeumannBCDefinition] | None | ||
initial_conditions: list[InitialConditionDefinition] | None | ||
body_forces: list[BodyForceDefinition] | None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only mechanics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only because of the body forces. Currently, each BC knows on which variable it should be applied, so the list of DirichletBCDefinition
can contain bcs for any problem. I am however not sure about this design decision. Either the BC knows where it belongs because of some property, or alternatively it could know this because of its type. This would mean that for each physical problem we would introduce another BC type like TemperatureBC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when you provide the dof (T, d) as additional parameter, then it is completely clear. Just have to make sure that the two have the same dimension.
body_forces: list[BodyForceDefinition] | None | ||
geometry: MeshGenerator | ||
solution_fields: list[str] | ||
time: tuple[float, float] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the time tuple, beginning and end?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. We could also do a list of timesteps for the minimal amount of load steps and then the solver decides if any in-between steps should be taken.
geometry: MeshGenerator | ||
solution_fields: list[str] | ||
time: tuple[float, float] | ||
material: MaterialDefinition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the material definition somehow has to be related to the geometry or physical components in case more than one material is used, which I think is something we should be able to cover.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Then the question is how we define a material. Is it just a string and then the solver needs to interpret that?
@@ -0,0 +1,73 @@ | |||
from pydantic.dataclasses import dataclass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my original idea, there was no general problem definition class apart from a very basic on with a solve functionality (to be coupled with probeye or other sensor readings) and potential parameters that are read in from a json file in addition to the experiment. The option to store sensor data in the simulation could IMO provided by a completely separate class that is initialized with an expeiment (thus potentially has all information and names from the sensors) and is called with a FEniCS solution and the time and then knows how to store the virtual sensor readings. Everything else is very user-specific. Or is there an advantage of adding more functionality (quadrature, interpolation order, ..) as general features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem.py
is more an example for trying out how a full json would look like.
I personally see benefits for storing everything that I added because in a workflow, I would could then edit a json that is read by the simulation script instead of changing the script.
As for what should always be contained in a problem, I would say that it should always be able to return sensor readings and it should always be able to plot all variables that are stored. The input can be an experiment and a dictionary of parameters. But apart from that, I am fine with not restricting what a problem can do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering what the advantage of the plotting capabilities in the problem is, instead of just adding that (and that is something everyone could decide himself) when creating the model. Then this class can be used afterwards to do the plotting (and does not even has to know the FE-problem), just the mesh, which is provided in the experiment. Then you can use any tool to visualize your results, potentially even in a postprocessing step (when having serialized the results) with a different software tool (visualizing ansys results with pyvista)
variable: The variable to be measured | ||
|
||
""" | ||
location: tuple[float,float,float] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are the units of the location? Always meter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not take care of units yet
|
||
""" | ||
location: tuple[float,float,float] | ||
variable: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should the variables be restricted to the predefined enum instead of general strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know yet. The enum would have the benefit that the input can very easily be checked, but it would also be restrictive. If you have a new material model with weird history variables, it would be easier to use the sting
unit: str | ||
|
||
@dataclass(config=dict(arbitrary_types_allowed=True)) | ||
class PlotDefinition: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what that class represents, a fieldSensor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically yes. It is different from a sensor because, it needs to write to a file with each measurement
|
||
def measure(self, t: float): | ||
for plot_function, function in zip(self.plot_functions.values(), self.functions.values()): | ||
if plot_function is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see benefits in making that an external class (not within the standard problem). Thus, you would have to pass the fenics data structure to be able to measure from that. However, I also see that it has benefits to integrate that into BaseProblem, in particular the passing of the solution fields. However, the latter is somehow more difficult to read, since the variables are defined in a different class than where they are used.
initial = InitialConditionDefinition(value=[42.24], variable='density') | ||
body_force = BodyForceDefinition(value=[0.,0., 9.81], variable='displacement') | ||
mat = LinearElasticMaterial(name='steel', mu=1., lam=2.) | ||
geo = MeshGenerator(parameters={'length': (1, 'm')}, mesh_tags={'left': 0, 'right': 1, 'top': 2, 'bottom': 3}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we cover the case that the mesh is given from other software (abaqus ...) or someone send me the mesh fiel and I have to use that? MeshGenerator = reading existing file and defining boundary groups (this would then be fenics stuff, or?)
bc = DirichletBCDefinition(marker=1, value=[0.,0.], subspace=0, variable='displacement') | ||
neumann = NeumannBCDefinition(marker=2, value=[-42.0], variable='displacement') | ||
initial = InitialConditionDefinition(value=[42.24], variable='density') | ||
body_force = BodyForceDefinition(value=[0.,0., 9.81], variable='displacement') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we ensure that the body force is applied in the correct direction? We had some problems on that in the current version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the users coordinate system and the three components of the body force I would expect that to be unique, what was the problem in the current version?
Stress = Quantity("Pa") | ||
Density = Quantity("kg/m^3") | ||
Time = Quantity("s") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would I use that if I have other, additional quantities? Would I create my own class AnnikaQuantity(Enum) or do we add that here? Some question for the SolutionFields Class ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have something that is general, I would add that here, if that is something really user specific, I would just define that in your own problem definition.
plots=[plot], | ||
element_type=[FiniteElementDefinition(name="P", geometry_order=1, function_order=1, variable="displacement")], | ||
quadrature_rule=[QuadratureRuleDefinition(name="Gauss", order=2, variable="displacement")], | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get where the real computation happens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think exactly here, but I guess this is currently missing in the proposal. Maybe having some linearElastic computation here would help to better understand the idea and how the poblem definition is then used. And I'm not yet sure what these inputs would be, and in particular, if FEMProblemDefinition is a BaseClass or just a class everyone is reimplementing according to their needs.
|
||
from typing import Callable | ||
from pydantic.dataclasses import dataclass | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only a general definition of the material, right? At which point do we implement what happens if you choose e.g linear elasticity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the level of your solver (FEMProblemDefinition -though the name is IMO too long). You would then built your weak form and have an additional configuration file (but that does not have to be general but is really depending on your problem) that includes the material parameters (if you always use linearElastic), or the constitutive law and the parameters (if you decide to be able to allow on the configuration file level different material laws).
@dataclass(config=dict(arbitrary_types_allowed=True)) | ||
class DirichletBCDefinition: | ||
""" | ||
Definition of a time- and position-dependent Dirichlet boundary condition. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would the Dirichlet boundary condition be defined in time? Just passing a np.ndarray or a function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would need to be a function Callable[[np.ndarray, float],np.ndarray]
. For simplicity, a numpy array would be interpreted as a constant BC at every point in time
plots: list[PlotDefinition] | ||
element_type: list[FiniteElementDefinition] | ||
quadrature_rule: list[QuadratureRuleDefinition] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, the different Experiment and Definition objects would only be used for initializing the problem and then can be forgotten, right? That would work with the model updating schemes, as then I can access directly the variables from FEMProblem (or the derive class) and update their values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say, that most attributes could be forgotten after initialization. However, I haven't really considered the implementation part of the problems yet.
groups: dict[str, list[PointSensorDefinition | GlobalSensorDefinition | PlotDefinition]] | ||
#plot_functions: dict[str, df.fem.Function | None] | ||
#functions: dict[str, df.fem.Function] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One functionality that is important for me is to be able to create sensors from metadata and export the sensors metadata as well. To export it, that would probably be just a new method here for the class Sensors. To import a sensor from metadata, I guess we would need to write a new function that adds it to the sensors list. That should not be a problem, but means keeping and updating the json schema to the dataclass structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of my design goals was that all classes have a very obvious representation as a dictionary/json, and you can just create a dataclass from a dictionary
sensor = Sensor(*dictionary)
Since the classes here only describe the sensors and have no methods, serializing and deserializing them should be what you are looking for
To continue the discussion from #158:
Everything is currently very WIP and I am not happy with everything, so please give feedback;)
The code does not really work as a package, but you can build the conda env and then run the
experiment.py
and theproblem.py
which currently just print a serialized JSON of the classes.