Coverage for nodimo / model.py: 100%
79 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-26 03:10 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-26 03:10 +0000
1# ┓• Licensed under the MIT License
2# ┏┓┏┓┏┫┓┏┳┓┏┓ Copyright (c) 2024 Rodrigo Castro
3# ┛┗┗┛┗┻┗┛┗┗┗┛ https://nodimo.readthedocs.io
5"""
6Model
7=====
9This module contains the classes to create (non)dimensional models.
11Classes
12-------
13Model
14 Creates a (non)dimensional model from a given set of quantities.
15"""
17from sympy import Number 1abcdef
18from itertools import combinations 1abcdef
20from nodimo.quantity import Quantity 1abcdef
21from nodimo.groups import DimensionalGroup, ScalingGroup 1abcdef
22from nodimo.relation import Relation 1abcdef
23from nodimo._internal import _print_horizontal_line, _unsympify_number 1abcdef
26class Model(Relation): 1abcdef
27 """(Non)dimensional model built from a given set of quantities.
29 This class builds relations from the resulting quantities of a group
30 transformation (DimensionalGroup). Every relation is associated with
31 a scaling group, which are built using the given scaling quantities.
33 Parameters
34 ----------
35 *quantities : Quantity
36 Quantities that constitute the model.
37 **dimensions : int
38 Aimed dimensions for the model given as keyword arguments.
40 Attributes
41 ----------
42 quantities : list[Quantity]
43 List with the quantities that constitute the model.
44 relations : dict[ScalingGroup, Relation]
45 Dictionary containing pairs of scaling groups and relations.
47 Methods
48 -------
49 show(use_custom_css=True, use_unicode=True)
50 Prints scaling groups and respective (non)dimensional relations.
52 Raises
53 ------
54 ValueError
55 If the number of scaling quantities is lower than necessary.
57 Warns
58 -----
59 NodimoWarning
60 Dimensions that are treated as independent.
62 Examples
63 --------
64 * Free fall
66 Dimensions: mass ``M``, length ``L`` and time ``T``.
68 Quantities: height ``z``, mass ``m``, velocity ``v``,
69 gravitational acceleration ``g``, time ``t``,
70 initial height ``z0`` and initial velocity ``v0``
72 Considering the scaling groups that can be formed with ``g``, ``z0``
73 and ``v0``, the ``model`` for ``z`` is built and displayed as:
75 >>> from nodimo import Quantity, Model
76 >>> z = Quantity('z', L=1, dependent=True)
77 >>> m = Quantity('m', M=1)
78 >>> v = Quantity('v', L=1, T=-1)
79 >>> g = Quantity('g', L=1, T=-2, scaling=True)
80 >>> t = Quantity('t', T=1)
81 >>> z0 = Quantity('z0', L=1, scaling=True)
82 >>> v0 = Quantity('v0', L=1, T=-1, scaling=True)
83 >>> model = Model(z, m, v, g, t, z0, v0)
84 >>> model.show()
85 """
87 def __init__(self, *quantities: Quantity, **dimensions: Number): 1abcdef
88 super().__init__(*quantities) 1abcdef
89 self._set_dimensions(**dimensions) 1abcdef
90 self._scaling_groups: list[ScalingGroup] 1abcdef
91 self._relations: dict[ScalingGroup, Relation] 1abcdef
92 self._set_model() 1abcdef
94 @property 1abcdef
95 def relations(self) -> dict[ScalingGroup, Relation]: 1abcdef
96 return self._relations 1abcdef
98 def _set_model(self): 1abcdef
99 self._set_matrix_rank() 1abcdef
100 self._set_scaling_quantities() 1abcdef
101 self._validate_model() 1abcdef
102 self._set_scaling_groups() 1abcdef
103 self._set_relations() 1abcdef
104 self._clear_null_dimensions() 1abcdef
106 def _validate_model(self): 1abcdef
107 if len(self._scaling_quantities) < self._rank: 1abcdef
108 raise ValueError( 1abcdef
109 f"The model must have at least {self._rank} scaling quantities"
110 )
112 def _set_scaling_groups(self): 1abcdef
113 scaling_groups = [] 1abcdef
114 for scgroup in combinations(self._scaling_quantities, self._rank): 1abcdef
115 try: 1abcdef
116 scaling_groups.append(ScalingGroup(*scgroup)) 1abcdef
117 except: 1abcdef
118 pass 1abcdef
120 if len(scaling_groups) > 1: 1abcdef
121 idnum = 1 1abcdef
122 for scgroup in scaling_groups: 1abcdef
123 scgroup._id_number = idnum 1abcdef
124 idnum += 1 1abcdef
126 self._scaling_groups = scaling_groups 1abcdef
128 def _set_relations(self): 1abcdef
129 relations = {} 1abcdef
130 for scgroup in self._scaling_groups: 1abcdef
131 quantities = list(self._nonscaling_quantities) 1abcdef
132 for qty in self._scaling_quantities: 1abcdef
133 if qty not in scgroup: 1abcdef
134 reset_qty = qty._copy() 1abcdef
135 reset_qty._is_scaling = False 1abcdef
136 quantities.append(reset_qty) 1abcdef
137 quantities.extend(scgroup.quantities) 1abcdef
139 dgroup = DimensionalGroup(*quantities, **self._dimensions) 1abcdef
140 for prod in dgroup.quantities: 1abcdef
141 if prod._is_product: 1abcdef
142 prod._is_dependent = any(qty.is_dependent for qty in prod.factors) 1abcdef
144 if len(self._scaling_groups) == 1: 1abcdef
145 relation_name = 'Phi' 1abcdef
146 else:
147 relation_name = f'Phi_{scgroup._id_number}' 1abcdef
149 relations[scgroup] = Relation(*dgroup.quantities, name=relation_name) 1abcdef
151 self._relations = relations 1abcdef
153 def show(self, use_custom_css: bool = True, use_unicode: bool = True): 1abcdef
154 super().show(use_custom_css=use_custom_css, use_unicode=use_unicode) 1abcdef
155 for i, (scgroup, relation) in enumerate(self._relations.items()): 1abcdef
156 _print_horizontal_line() 1abcdef
157 scgroup.show(use_custom_css=False, use_unicode=use_unicode) 1abcdef
158 relation.show(use_custom_css=use_custom_css, use_unicode=use_unicode) 1abcdef
160 def _key(self) -> tuple: 1abcdef
161 return ( 1abcdef
162 frozenset(self._scaling_quantities),
163 frozenset(self._nonscaling_quantities),
164 frozenset(self._dimensions.items()),
165 )
167 def _sympyrepr(self, printer) -> str: 1abcdef
168 class_name = type(self).__name__ 1abcdef
169 quantities = ', '.join(printer._print(qty) for qty in self._quantities) 1abcdef
171 if self._is_dimensionless: 1abcdef
172 dimensions = '' 1abcdef
173 else:
174 dims = [] 1abcdef
175 for dim_name, dim_exp in self._dimensions.items(): 1abcdef
176 dim_exp_ = _unsympify_number(dim_exp) 1abcdef
177 if isinstance(dim_exp_, str): 1abcdef
178 dim_exp_ = f"'{dim_exp_}'" 1abcdef
179 dims.append(f'{dim_name}={dim_exp_}') 1abcdef
180 dimensions = f", {', '.join(dims)}" 1abcdef
182 return f'{class_name}({quantities}{dimensions})' 1abcdef