pacejka 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pacejka-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 2023popn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include LICENSE README.md
2
+ recursive-include tests *.py
3
+ prune sweep_output
4
+ global-exclude __pycache__ *.py[cod]
pacejka-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: pacejka
3
+ Version: 0.1.0
4
+ Summary: Calculate tire forces with the Pacejka magic formula
5
+ Author: 2023popn
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/2023popn/Pacejka
8
+ Project-URL: Issues, https://github.com/2023popn/Pacejka/issues
9
+ Keywords: tire,pacejka,magic formula,vehicle dynamics,motorsport
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Topic :: Scientific/Engineering :: Physics
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy>=1.20
25
+ Provides-Extra: plots
26
+ Requires-Dist: matplotlib>=3.5; extra == "plots"
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=7; extra == "test"
29
+ Requires-Dist: matplotlib>=3.5; extra == "test"
30
+ Dynamic: license-file
31
+
32
+ # Pacejka
33
+
34
+ Calculate the forces a tire generates with the Pacejka "magic formula".
35
+
36
+ Describe a tire by its peak friction, cornering and longitudinal stiffness, and how its grip changes with load. Then ask for the lateral and longitudinal force at any vertical load, slip angle and slip ratio. Everything works on single numbers or on whole numpy arrays, so a full slip-angle sweep or a load-versus-slip grid is one call.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install pacejka
42
+ ```
43
+
44
+ The only dependency is numpy. To run the plotting tests too, install the `test` extra (see [Development](#development)).
45
+
46
+ The package is installed as `pacejka` and imported as `Pacejka`:
47
+
48
+ ```python
49
+ from pacejka import Tire
50
+ ```
51
+
52
+ ## Quick start
53
+
54
+ ```python
55
+ from pacejka import Tire
56
+
57
+ tire = Tire.from_macro_properties(
58
+ "Hoosier R55A",
59
+ mu_base=1.6, # peak friction coefficient at the reference load
60
+ cornering_stiffness_per_deg=25.0, # lateral force per degree of slip angle (small angles)
61
+ longitudinal_stiffness=2000.0, # longitudinal force per unit slip ratio (small slip)
62
+ load_sensitivity_n=0.9, # how much grip per unit load falls as load rises
63
+ f_z0=150.0, # reference load the stiffnesses are quoted at
64
+ )
65
+
66
+ fy, fx = tire.calculate_forces(f_z=150.0, alpha=4.0, kappa=0.05, alpha_unit="deg")
67
+ print(f"lateral = {fy:.1f}, longitudinal = {fx:.1f}")
68
+ ```
69
+
70
+ ```
71
+ lateral = 94.8, longitudinal = 94.3
72
+ ```
73
+
74
+ `calculate_forces` returns `(lateral_force, longitudinal_force)`. Forces come out in whatever unit you use for load, so pounds in gives pounds-force out and newtons in gives newtons out.
75
+
76
+ If you only need the lateral force:
77
+
78
+ ```python
79
+ fy = tire.calculate_lateral_force(150.0, 4.0, alpha_unit="deg")
80
+ ```
81
+
82
+ ## Units
83
+
84
+ | Input | Unit |
85
+ |---|---|
86
+ | `f_z` (vertical load) | any force unit; forces come back in the same unit |
87
+ | `alpha` (slip angle) | radians or degrees, see below |
88
+ | `kappa` (slip ratio) | dimensionless, `1.0` is 100 % slip |
89
+ | `cornering_stiffness_per_deg` / `_per_rad` | force per degree / radian of slip angle |
90
+ | `longitudinal_stiffness` | force per unit slip ratio |
91
+
92
+ ### Slip angle: radians or degrees
93
+
94
+ State the unit of `alpha` explicitly with `alpha_unit="rad"` or `alpha_unit="deg"`. This is the safest option.
95
+
96
+ If you leave `alpha_unit` out, the unit is detected: the angle is taken to be radians unless any value has a magnitude above π/2 (about 1.57), in which case the **whole** input is treated as degrees. A sweep from −15 to 15 is therefore read correctly as degrees, but small angles in degrees are not (an angle of `0.5` looks like radians). When in doubt, pass `alpha_unit`.
97
+
98
+ ```python
99
+ tire.calculate_lateral_force(150.0, 4.0) # detected: 4.0 is degrees
100
+ tire.calculate_lateral_force(150.0, 0.5) # detected: 0.5 is radians -> 239.8
101
+ tire.calculate_lateral_force(150.0, 0.5, alpha_unit="deg") # explicitly degrees -> 12.5
102
+ ```
103
+
104
+ ### Cornering stiffness: per degree or per radian
105
+
106
+ `from_macro_properties` takes either `cornering_stiffness_per_deg` or `cornering_stiffness_per_rad` (exactly one). The `Tire` constructor itself works in radians only.
107
+
108
+ ## Sweeps and grids with arrays
109
+
110
+ Pass arrays for any of `f_z`, `alpha` and `kappa`. numpy broadcasting combines them.
111
+
112
+ A slip-angle sweep at one load:
113
+
114
+ ```python
115
+ import numpy as np
116
+
117
+ alpha = np.linspace(-15, 15, 7)
118
+ fy = tire.calculate_lateral_force(150.0, alpha, alpha_unit="deg")
119
+ print(np.round(fy, 1))
120
+ ```
121
+
122
+ ```
123
+ [-223.1 -188.7 -115.2 0. 115.2 188.7 223.1]
124
+ ```
125
+
126
+ A grid with one row per load and one column per slip angle:
127
+
128
+ ```python
129
+ loads = np.array([[100.0], [150.0], [200.0]]) # a column
130
+ alpha = np.array([2.0, 5.0, 10.0]) # a row
131
+ grid = tire.calculate_lateral_force(loads, alpha, alpha_unit="deg")
132
+ print(grid.shape)
133
+ print(np.round(grid, 1))
134
+ ```
135
+
136
+ ```
137
+ (3, 3)
138
+ [[ 34.2 80. 131. ]
139
+ [ 49.3 115.2 188.7]
140
+ [ 63.9 149.2 244.5]]
141
+ ```
142
+
143
+ Plotting a sweep with matplotlib:
144
+
145
+ ```python
146
+ import matplotlib.pyplot as plt
147
+
148
+ alpha = np.linspace(-30, 30, 400)
149
+ for f_z in (50, 100, 150, 200, 250):
150
+ plt.plot(alpha, tire.calculate_lateral_force(f_z, alpha, alpha_unit="deg"), label=f"{f_z}")
151
+ plt.xlabel("Slip angle (deg)")
152
+ plt.ylabel("Lateral force")
153
+ plt.legend()
154
+ plt.show()
155
+ ```
156
+
157
+ A scalar in gives a scalar out. Lists work as well as arrays.
158
+
159
+ > When `friction_circle=False`, each force has the shape of its own inputs (lateral from `f_z` and `alpha`, longitudinal from `f_z` and `kappa`). With the friction circle on (the default), both forces have the full broadcast shape.
160
+
161
+ ## The friction circle
162
+
163
+ By default, `calculate_forces` limits the combined force so it cannot exceed the tire's peak force at that load. If the lateral and longitudinal forces together are too large, both are scaled down by the same factor, which keeps their direction.
164
+
165
+ ```python
166
+ for circle in (False, True):
167
+ fy, fx = tire.calculate_forces(150.0, 8.0, 0.3, friction_circle=circle, alpha_unit="deg")
168
+ print(f"friction_circle={circle}: fy={fy:.1f} fx={fx:.1f} total={np.hypot(fx, fy):.1f}")
169
+ ```
170
+
171
+ ```
172
+ friction_circle=False: fy=164.9 fx=238.5 total=290.0
173
+ friction_circle=True: fy=136.5 fx=197.4 total=240.0
174
+ ```
175
+
176
+ The limit here is 240, which is the peak force at this load (`tire.calculate_d(150.0)`). Pass `friction_circle=False` for the pure, independent lateral and longitudinal forces.
177
+
178
+ ## Building a tire
179
+
180
+ **From measurable properties** (most common). B is derived from your stiffnesses at each load, and grip changes with load:
181
+
182
+ ```python
183
+ Tire.from_macro_properties("name", mu_base=1.6, cornering_stiffness_per_deg=25.0,
184
+ longitudinal_stiffness=2000.0, load_sensitivity_n=0.9, f_z0=150.0)
185
+ ```
186
+
187
+ `load_sensitivity_n` of `1.0` means grip per unit load is constant. Lower values mean it falls as load increases (`0.9` is roughly a 10 % drop per doubling of load). `f_z0` is the load at which the stiffnesses and `mu_base` apply.
188
+
189
+ **From fixed magic formula constants.** You supply B, C and E directly, and there is no load sensitivity (peak force is `mu_base * f_z`). Lateral values default to the longitudinal ones if you leave them out:
190
+
191
+ ```python
192
+ fixed_tire = Tire.from_constants("Fixed", mu_base=1.5, b_x=8.0, c_x=1.4, e_x=0.1)
193
+ fy, fx = fixed_tire.calculate_forces(200.0, 0.1, 0.05, alpha_unit="rad")
194
+ ```
195
+
196
+ **Directly** with `Tire(...)`, which takes cornering stiffness per radian and the other values by keyword.
197
+
198
+ ## Looking at the coefficients
199
+
200
+ `coefficients_at(f_z)` returns the full set of magic formula coefficients for each axis at a given load, as `(longitudinal, lateral)`:
201
+
202
+ ```python
203
+ longitudinal, lateral = tire.coefficients_at(150.0)
204
+ print(lateral)
205
+ ```
206
+
207
+ ```
208
+ MagicFormulaCoefficients(b=4.263078832818626, c=1.4, d=240.0, e=-0.1)
209
+ ```
210
+
211
+ These are immutable and freshly built on every call. A `Tire` never changes when you ask it for forces, so one tire can safely be shared between threads. The fixed settings that belong to the tire are on `tire.longitudinal` and `tire.lateral`, for example `AxisShape(c=1.4, e=-0.1, b=None)`.
212
+
213
+ ## How it works
214
+
215
+ For each axis, with `x` the slip angle in radians (lateral) or the slip ratio (longitudinal):
216
+
217
+ ```
218
+ F = D * sin( C * arctan( B*x - E * (B*x - arctan(B*x)) ) )
219
+
220
+ D = mu_peak * (Fz / Fz0) ** (n - 1) * Fz peak force at load Fz
221
+ B = stiffness * (Fz / Fz0) ** n / (C * D) from the small-slip stiffness
222
+ ```
223
+
224
+ `C` (shape) and `E` (curvature) default to 1.4 and 0.1 (longitudinal) or -0.1 (lateral). D is the same for both axes. A load of zero or less produces no force.
225
+
226
+ ## Limitations
227
+
228
+ - These are the pure-slip magic formulas combined with a friction circle, not the full combined-slip Pacejka formulation. Lateral force does not depend on slip ratio except through the friction circle limit.
229
+ - No camber, inflation pressure, speed, temperature or aligning moment.
230
+ - The effective friction coefficient follows a power law in load, so it grows without bound as load approaches zero. Peak force still goes to zero.
231
+ - Tire properties are your inputs. The package does not ship measured tire data, so check your coefficients against your own data.
232
+
233
+ ## Development
234
+
235
+ ```bash
236
+ git clone https://github.com/2023popn/Pacejka
237
+ cd pacejka
238
+ pip install -e ".[test]"
239
+ pytest
240
+ ```
241
+
242
+ The tests include **sweeps**: plots of lateral force against slip angle, longitudinal force against slip ratio, load sensitivity, the friction circle, and a load-versus-slip grid. They are saved as PNG files in `sweep_output/` (or the folder given by `--sweep-dir`) so you can look at the curves, and each also checks the shape of what it plots.
243
+
244
+ ```bash
245
+ pytest --sweep-dir=plots # save the sweep plots somewhere else
246
+ pytest -m "not sweep" # skip the plotting tests
247
+ ```
248
+
249
+ The sweep tests need matplotlib and are skipped if it is not installed.
250
+
251
+ ## License
252
+
253
+ MIT. See `LICENSE`.
@@ -0,0 +1,222 @@
1
+ # Pacejka
2
+
3
+ Calculate the forces a tire generates with the Pacejka "magic formula".
4
+
5
+ Describe a tire by its peak friction, cornering and longitudinal stiffness, and how its grip changes with load. Then ask for the lateral and longitudinal force at any vertical load, slip angle and slip ratio. Everything works on single numbers or on whole numpy arrays, so a full slip-angle sweep or a load-versus-slip grid is one call.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install pacejka
11
+ ```
12
+
13
+ The only dependency is numpy. To run the plotting tests too, install the `test` extra (see [Development](#development)).
14
+
15
+ The package is installed as `pacejka` and imported as `Pacejka`:
16
+
17
+ ```python
18
+ from pacejka import Tire
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ```python
24
+ from pacejka import Tire
25
+
26
+ tire = Tire.from_macro_properties(
27
+ "Hoosier R55A",
28
+ mu_base=1.6, # peak friction coefficient at the reference load
29
+ cornering_stiffness_per_deg=25.0, # lateral force per degree of slip angle (small angles)
30
+ longitudinal_stiffness=2000.0, # longitudinal force per unit slip ratio (small slip)
31
+ load_sensitivity_n=0.9, # how much grip per unit load falls as load rises
32
+ f_z0=150.0, # reference load the stiffnesses are quoted at
33
+ )
34
+
35
+ fy, fx = tire.calculate_forces(f_z=150.0, alpha=4.0, kappa=0.05, alpha_unit="deg")
36
+ print(f"lateral = {fy:.1f}, longitudinal = {fx:.1f}")
37
+ ```
38
+
39
+ ```
40
+ lateral = 94.8, longitudinal = 94.3
41
+ ```
42
+
43
+ `calculate_forces` returns `(lateral_force, longitudinal_force)`. Forces come out in whatever unit you use for load, so pounds in gives pounds-force out and newtons in gives newtons out.
44
+
45
+ If you only need the lateral force:
46
+
47
+ ```python
48
+ fy = tire.calculate_lateral_force(150.0, 4.0, alpha_unit="deg")
49
+ ```
50
+
51
+ ## Units
52
+
53
+ | Input | Unit |
54
+ |---|---|
55
+ | `f_z` (vertical load) | any force unit; forces come back in the same unit |
56
+ | `alpha` (slip angle) | radians or degrees, see below |
57
+ | `kappa` (slip ratio) | dimensionless, `1.0` is 100 % slip |
58
+ | `cornering_stiffness_per_deg` / `_per_rad` | force per degree / radian of slip angle |
59
+ | `longitudinal_stiffness` | force per unit slip ratio |
60
+
61
+ ### Slip angle: radians or degrees
62
+
63
+ State the unit of `alpha` explicitly with `alpha_unit="rad"` or `alpha_unit="deg"`. This is the safest option.
64
+
65
+ If you leave `alpha_unit` out, the unit is detected: the angle is taken to be radians unless any value has a magnitude above π/2 (about 1.57), in which case the **whole** input is treated as degrees. A sweep from −15 to 15 is therefore read correctly as degrees, but small angles in degrees are not (an angle of `0.5` looks like radians). When in doubt, pass `alpha_unit`.
66
+
67
+ ```python
68
+ tire.calculate_lateral_force(150.0, 4.0) # detected: 4.0 is degrees
69
+ tire.calculate_lateral_force(150.0, 0.5) # detected: 0.5 is radians -> 239.8
70
+ tire.calculate_lateral_force(150.0, 0.5, alpha_unit="deg") # explicitly degrees -> 12.5
71
+ ```
72
+
73
+ ### Cornering stiffness: per degree or per radian
74
+
75
+ `from_macro_properties` takes either `cornering_stiffness_per_deg` or `cornering_stiffness_per_rad` (exactly one). The `Tire` constructor itself works in radians only.
76
+
77
+ ## Sweeps and grids with arrays
78
+
79
+ Pass arrays for any of `f_z`, `alpha` and `kappa`. numpy broadcasting combines them.
80
+
81
+ A slip-angle sweep at one load:
82
+
83
+ ```python
84
+ import numpy as np
85
+
86
+ alpha = np.linspace(-15, 15, 7)
87
+ fy = tire.calculate_lateral_force(150.0, alpha, alpha_unit="deg")
88
+ print(np.round(fy, 1))
89
+ ```
90
+
91
+ ```
92
+ [-223.1 -188.7 -115.2 0. 115.2 188.7 223.1]
93
+ ```
94
+
95
+ A grid with one row per load and one column per slip angle:
96
+
97
+ ```python
98
+ loads = np.array([[100.0], [150.0], [200.0]]) # a column
99
+ alpha = np.array([2.0, 5.0, 10.0]) # a row
100
+ grid = tire.calculate_lateral_force(loads, alpha, alpha_unit="deg")
101
+ print(grid.shape)
102
+ print(np.round(grid, 1))
103
+ ```
104
+
105
+ ```
106
+ (3, 3)
107
+ [[ 34.2 80. 131. ]
108
+ [ 49.3 115.2 188.7]
109
+ [ 63.9 149.2 244.5]]
110
+ ```
111
+
112
+ Plotting a sweep with matplotlib:
113
+
114
+ ```python
115
+ import matplotlib.pyplot as plt
116
+
117
+ alpha = np.linspace(-30, 30, 400)
118
+ for f_z in (50, 100, 150, 200, 250):
119
+ plt.plot(alpha, tire.calculate_lateral_force(f_z, alpha, alpha_unit="deg"), label=f"{f_z}")
120
+ plt.xlabel("Slip angle (deg)")
121
+ plt.ylabel("Lateral force")
122
+ plt.legend()
123
+ plt.show()
124
+ ```
125
+
126
+ A scalar in gives a scalar out. Lists work as well as arrays.
127
+
128
+ > When `friction_circle=False`, each force has the shape of its own inputs (lateral from `f_z` and `alpha`, longitudinal from `f_z` and `kappa`). With the friction circle on (the default), both forces have the full broadcast shape.
129
+
130
+ ## The friction circle
131
+
132
+ By default, `calculate_forces` limits the combined force so it cannot exceed the tire's peak force at that load. If the lateral and longitudinal forces together are too large, both are scaled down by the same factor, which keeps their direction.
133
+
134
+ ```python
135
+ for circle in (False, True):
136
+ fy, fx = tire.calculate_forces(150.0, 8.0, 0.3, friction_circle=circle, alpha_unit="deg")
137
+ print(f"friction_circle={circle}: fy={fy:.1f} fx={fx:.1f} total={np.hypot(fx, fy):.1f}")
138
+ ```
139
+
140
+ ```
141
+ friction_circle=False: fy=164.9 fx=238.5 total=290.0
142
+ friction_circle=True: fy=136.5 fx=197.4 total=240.0
143
+ ```
144
+
145
+ The limit here is 240, which is the peak force at this load (`tire.calculate_d(150.0)`). Pass `friction_circle=False` for the pure, independent lateral and longitudinal forces.
146
+
147
+ ## Building a tire
148
+
149
+ **From measurable properties** (most common). B is derived from your stiffnesses at each load, and grip changes with load:
150
+
151
+ ```python
152
+ Tire.from_macro_properties("name", mu_base=1.6, cornering_stiffness_per_deg=25.0,
153
+ longitudinal_stiffness=2000.0, load_sensitivity_n=0.9, f_z0=150.0)
154
+ ```
155
+
156
+ `load_sensitivity_n` of `1.0` means grip per unit load is constant. Lower values mean it falls as load increases (`0.9` is roughly a 10 % drop per doubling of load). `f_z0` is the load at which the stiffnesses and `mu_base` apply.
157
+
158
+ **From fixed magic formula constants.** You supply B, C and E directly, and there is no load sensitivity (peak force is `mu_base * f_z`). Lateral values default to the longitudinal ones if you leave them out:
159
+
160
+ ```python
161
+ fixed_tire = Tire.from_constants("Fixed", mu_base=1.5, b_x=8.0, c_x=1.4, e_x=0.1)
162
+ fy, fx = fixed_tire.calculate_forces(200.0, 0.1, 0.05, alpha_unit="rad")
163
+ ```
164
+
165
+ **Directly** with `Tire(...)`, which takes cornering stiffness per radian and the other values by keyword.
166
+
167
+ ## Looking at the coefficients
168
+
169
+ `coefficients_at(f_z)` returns the full set of magic formula coefficients for each axis at a given load, as `(longitudinal, lateral)`:
170
+
171
+ ```python
172
+ longitudinal, lateral = tire.coefficients_at(150.0)
173
+ print(lateral)
174
+ ```
175
+
176
+ ```
177
+ MagicFormulaCoefficients(b=4.263078832818626, c=1.4, d=240.0, e=-0.1)
178
+ ```
179
+
180
+ These are immutable and freshly built on every call. A `Tire` never changes when you ask it for forces, so one tire can safely be shared between threads. The fixed settings that belong to the tire are on `tire.longitudinal` and `tire.lateral`, for example `AxisShape(c=1.4, e=-0.1, b=None)`.
181
+
182
+ ## How it works
183
+
184
+ For each axis, with `x` the slip angle in radians (lateral) or the slip ratio (longitudinal):
185
+
186
+ ```
187
+ F = D * sin( C * arctan( B*x - E * (B*x - arctan(B*x)) ) )
188
+
189
+ D = mu_peak * (Fz / Fz0) ** (n - 1) * Fz peak force at load Fz
190
+ B = stiffness * (Fz / Fz0) ** n / (C * D) from the small-slip stiffness
191
+ ```
192
+
193
+ `C` (shape) and `E` (curvature) default to 1.4 and 0.1 (longitudinal) or -0.1 (lateral). D is the same for both axes. A load of zero or less produces no force.
194
+
195
+ ## Limitations
196
+
197
+ - These are the pure-slip magic formulas combined with a friction circle, not the full combined-slip Pacejka formulation. Lateral force does not depend on slip ratio except through the friction circle limit.
198
+ - No camber, inflation pressure, speed, temperature or aligning moment.
199
+ - The effective friction coefficient follows a power law in load, so it grows without bound as load approaches zero. Peak force still goes to zero.
200
+ - Tire properties are your inputs. The package does not ship measured tire data, so check your coefficients against your own data.
201
+
202
+ ## Development
203
+
204
+ ```bash
205
+ git clone https://github.com/2023popn/Pacejka
206
+ cd pacejka
207
+ pip install -e ".[test]"
208
+ pytest
209
+ ```
210
+
211
+ The tests include **sweeps**: plots of lateral force against slip angle, longitudinal force against slip ratio, load sensitivity, the friction circle, and a load-versus-slip grid. They are saved as PNG files in `sweep_output/` (or the folder given by `--sweep-dir`) so you can look at the curves, and each also checks the shape of what it plots.
212
+
213
+ ```bash
214
+ pytest --sweep-dir=plots # save the sweep plots somewhere else
215
+ pytest -m "not sweep" # skip the plotting tests
216
+ ```
217
+
218
+ The sweep tests need matplotlib and are skipped if it is not installed.
219
+
220
+ ## License
221
+
222
+ MIT. See `LICENSE`.
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pacejka"
7
+ dynamic = ["version"]
8
+ description = "Calculate tire forces with the Pacejka magic formula"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "2023popn" }]
14
+ keywords = ["tire", "pacejka", "magic formula", "vehicle dynamics", "motorsport"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Science/Research",
18
+ "Intended Audience :: Education",
19
+ "Topic :: Scientific/Engineering :: Physics",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = ["numpy>=1.20"]
29
+
30
+ [project.optional-dependencies]
31
+ plots = ["matplotlib>=3.5"]
32
+ test = ["pytest>=7", "matplotlib>=3.5"]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/2023popn/Pacejka"
36
+ Issues = "https://github.com/2023popn/Pacejka/issues"
37
+
38
+ [tool.setuptools.dynamic]
39
+ version = { attr = "pacejka.__version__" }
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.setuptools.package-data]
45
+ pacejka = ["py.typed"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ addopts = "-ra"
50
+ markers = [
51
+ "sweep: saves a plot of a parameter sweep (needs matplotlib); deselect with -m 'not sweep'",
52
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """pacejka: tire force calculations using the pacejka magic formula."""
2
+
3
+ from .tire import AxisShape, MagicFormulaCoefficients, Tire
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = ["AxisShape", "MagicFormulaCoefficients", "Tire", "__version__"]
File without changes