├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── dev
├── example1.png
├── example2.png
├── example3.png
├── fit.gif
├── fit2.gif
└── icon
│ ├── icon.bat
│ ├── icon.ico
│ ├── icon.png
│ └── icon.svg
└── src
├── SwarmFit.Benchmarks
├── Program.cs
├── Simple.cs
├── SwarmFit.Benchmarks.csproj
└── run.bat
├── SwarmFit.Demo
├── Form1.Designer.cs
├── Form1.cs
├── Form1.resx
├── Plotting.cs
├── Program.cs
└── SwarmFit.Demo.csproj
├── SwarmFit.Tests
├── Animation.cs
├── KnownSolutions.cs
├── Plotting.cs
├── Quickstart.cs
├── RandomNumberGeneratorTests.cs
└── SwarmFit.Tests.csproj
├── SwarmFit.sln
└── SwarmFit
├── Extensions.cs
├── FitSolution.cs
├── IFunction.cs
├── IRandomNumberGenerator.cs
├── ParameterLimits.cs
├── Particle.cs
├── QuickFit.cs
├── RandomNumberGenerators
├── CryptoRandom.cs
├── SystemRandom.cs
└── XorShift.cs
├── SampleData.cs
├── StandardFunction.cs
├── StandardFunctions
├── ExpDec1.cs
├── ExpDecay1.cs
└── Linear.cs
├── SwarmFit.csproj
├── SwarmFitter.cs
├── icon.png
└── nuget-readme.md
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | pull_request:
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Setup dotnet
14 | uses: actions/setup-dotnet@v3
15 | - name: Setup Validator
16 | run: dotnet tool install --global dotnet-validate --version 0.0.1-preview.304
17 | - name: Restore
18 | run: dotnet restore ./src
19 | - name: Build
20 | run: dotnet build ./src
21 | - name: Test
22 | run: dotnet test ./src
23 | - name: Pack
24 | run: dotnet pack ./src/SwarmFit/ -o packages
25 | - name: Validate Packages
26 | run: dotnet validate package local ./packages/*.nupkg
27 | - name: Setup NuGet
28 | if: github.ref == 'refs/heads/main'
29 | uses: nuget/setup-nuget@v2
30 | with:
31 | nuget-api-key: ${{ secrets.NUGET_API_KEY }}
32 | - name: Deploy NuGet Packages
33 | if: github.ref == 'refs/heads/main'
34 | run: nuget push packages/*.nupkg -SkipDuplicate -Source https://api.nuget.org/v3/index.json
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /packages/
2 |
3 | ## Ignore Visual Studio temporary files, build results, and
4 | ## files generated by popular Visual Studio add-ons.
5 | ##
6 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
7 |
8 | # User-specific files
9 | *.rsuser
10 | *.suo
11 | *.user
12 | *.userosscache
13 | *.sln.docstates
14 |
15 | # User-specific files (MonoDevelop/Xamarin Studio)
16 | *.userprefs
17 |
18 | # Mono auto generated files
19 | mono_crash.*
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Dd]ebugPublic/
24 | [Rr]elease/
25 | [Rr]eleases/
26 | x64/
27 | x86/
28 | [Ww][Ii][Nn]32/
29 | [Aa][Rr][Mm]/
30 | [Aa][Rr][Mm]64/
31 | bld/
32 | [Bb]in/
33 | [Oo]bj/
34 | [Ll]og/
35 | [Ll]ogs/
36 |
37 | # Visual Studio 2015/2017 cache/options directory
38 | .vs/
39 | # Uncomment if you have tasks that create the project's static files in wwwroot
40 | #wwwroot/
41 |
42 | # Visual Studio 2017 auto generated files
43 | Generated\ Files/
44 |
45 | # MSTest test Results
46 | [Tt]est[Rr]esult*/
47 | [Bb]uild[Ll]og.*
48 |
49 | # NUnit
50 | *.VisualState.xml
51 | TestResult.xml
52 | nunit-*.xml
53 |
54 | # Build Results of an ATL Project
55 | [Dd]ebugPS/
56 | [Rr]eleasePS/
57 | dlldata.c
58 |
59 | # Benchmark Results
60 | BenchmarkDotNet.Artifacts/
61 |
62 | # .NET Core
63 | project.lock.json
64 | project.fragment.lock.json
65 | artifacts/
66 |
67 | # ASP.NET Scaffolding
68 | ScaffoldingReadMe.txt
69 |
70 | # StyleCop
71 | StyleCopReport.xml
72 |
73 | # Files built by Visual Studio
74 | *_i.c
75 | *_p.c
76 | *_h.h
77 | *.ilk
78 | *.meta
79 | *.obj
80 | *.iobj
81 | *.pch
82 | *.pdb
83 | *.ipdb
84 | *.pgc
85 | *.pgd
86 | *.rsp
87 | *.sbr
88 | *.tlb
89 | *.tli
90 | *.tlh
91 | *.tmp
92 | *.tmp_proj
93 | *_wpftmp.csproj
94 | *.log
95 | *.tlog
96 | *.vspscc
97 | *.vssscc
98 | .builds
99 | *.pidb
100 | *.svclog
101 | *.scc
102 |
103 | # Chutzpah Test files
104 | _Chutzpah*
105 |
106 | # Visual C++ cache files
107 | ipch/
108 | *.aps
109 | *.ncb
110 | *.opendb
111 | *.opensdf
112 | *.sdf
113 | *.cachefile
114 | *.VC.db
115 | *.VC.VC.opendb
116 |
117 | # Visual Studio profiler
118 | *.psess
119 | *.vsp
120 | *.vspx
121 | *.sap
122 |
123 | # Visual Studio Trace Files
124 | *.e2e
125 |
126 | # TFS 2012 Local Workspace
127 | $tf/
128 |
129 | # Guidance Automation Toolkit
130 | *.gpState
131 |
132 | # ReSharper is a .NET coding add-in
133 | _ReSharper*/
134 | *.[Rr]e[Ss]harper
135 | *.DotSettings.user
136 |
137 | # TeamCity is a build add-in
138 | _TeamCity*
139 |
140 | # DotCover is a Code Coverage Tool
141 | *.dotCover
142 |
143 | # AxoCover is a Code Coverage Tool
144 | .axoCover/*
145 | !.axoCover/settings.json
146 |
147 | # Coverlet is a free, cross platform Code Coverage Tool
148 | coverage*.json
149 | coverage*.xml
150 | coverage*.info
151 |
152 | # Visual Studio code coverage results
153 | *.coverage
154 | *.coveragexml
155 |
156 | # NCrunch
157 | _NCrunch_*
158 | .*crunch*.local.xml
159 | nCrunchTemp_*
160 |
161 | # MightyMoose
162 | *.mm.*
163 | AutoTest.Net/
164 |
165 | # Web workbench (sass)
166 | .sass-cache/
167 |
168 | # Installshield output folder
169 | [Ee]xpress/
170 |
171 | # DocProject is a documentation generator add-in
172 | DocProject/buildhelp/
173 | DocProject/Help/*.HxT
174 | DocProject/Help/*.HxC
175 | DocProject/Help/*.hhc
176 | DocProject/Help/*.hhk
177 | DocProject/Help/*.hhp
178 | DocProject/Help/Html2
179 | DocProject/Help/html
180 |
181 | # Click-Once directory
182 | publish/
183 |
184 | # Publish Web Output
185 | *.[Pp]ublish.xml
186 | *.azurePubxml
187 | # Note: Comment the next line if you want to checkin your web deploy settings,
188 | # but database connection strings (with potential passwords) will be unencrypted
189 | *.pubxml
190 | *.publishproj
191 |
192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
193 | # checkin your Azure Web App publish settings, but sensitive information contained
194 | # in these scripts will be unencrypted
195 | PublishScripts/
196 |
197 | # NuGet Packages
198 | *.nupkg
199 | # NuGet Symbol Packages
200 | *.snupkg
201 | # The packages folder can be ignored because of Package Restore
202 | **/[Pp]ackages/*
203 | # except build/, which is used as an MSBuild target.
204 | !**/[Pp]ackages/build/
205 | # Uncomment if necessary however generally it will be regenerated when needed
206 | #!**/[Pp]ackages/repositories.config
207 | # NuGet v3's project.json files produces more ignorable files
208 | *.nuget.props
209 | *.nuget.targets
210 |
211 | # Microsoft Azure Build Output
212 | csx/
213 | *.build.csdef
214 |
215 | # Microsoft Azure Emulator
216 | ecf/
217 | rcf/
218 |
219 | # Windows Store app package directories and files
220 | AppPackages/
221 | BundleArtifacts/
222 | Package.StoreAssociation.xml
223 | _pkginfo.txt
224 | *.appx
225 | *.appxbundle
226 | *.appxupload
227 |
228 | # Visual Studio cache files
229 | # files ending in .cache can be ignored
230 | *.[Cc]ache
231 | # but keep track of directories ending in .cache
232 | !?*.[Cc]ache/
233 |
234 | # Others
235 | ClientBin/
236 | ~$*
237 | *~
238 | *.dbmdl
239 | *.dbproj.schemaview
240 | *.jfm
241 | *.pfx
242 | *.publishsettings
243 | orleans.codegen.cs
244 |
245 | # Including strong name files can present a security risk
246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
247 | #*.snk
248 |
249 | # Since there are multiple workflows, uncomment next line to ignore bower_components
250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
251 | #bower_components/
252 |
253 | # RIA/Silverlight projects
254 | Generated_Code/
255 |
256 | # Backup & report files from converting an old project file
257 | # to a newer Visual Studio version. Backup files are not needed,
258 | # because we have git ;-)
259 | _UpgradeReport_Files/
260 | Backup*/
261 | UpgradeLog*.XML
262 | UpgradeLog*.htm
263 | ServiceFabricBackup/
264 | *.rptproj.bak
265 |
266 | # SQL Server files
267 | *.mdf
268 | *.ldf
269 | *.ndf
270 |
271 | # Business Intelligence projects
272 | *.rdl.data
273 | *.bim.layout
274 | *.bim_*.settings
275 | *.rptproj.rsuser
276 | *- [Bb]ackup.rdl
277 | *- [Bb]ackup ([0-9]).rdl
278 | *- [Bb]ackup ([0-9][0-9]).rdl
279 |
280 | # Microsoft Fakes
281 | FakesAssemblies/
282 |
283 | # GhostDoc plugin setting file
284 | *.GhostDoc.xml
285 |
286 | # Node.js Tools for Visual Studio
287 | .ntvs_analysis.dat
288 | node_modules/
289 |
290 | # Visual Studio 6 build log
291 | *.plg
292 |
293 | # Visual Studio 6 workspace options file
294 | *.opt
295 |
296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
297 | *.vbw
298 |
299 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
300 | *.vbp
301 |
302 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
303 | *.dsw
304 | *.dsp
305 |
306 | # Visual Studio 6 technical files
307 | *.ncb
308 | *.aps
309 |
310 | # Visual Studio LightSwitch build output
311 | **/*.HTMLClient/GeneratedArtifacts
312 | **/*.DesktopClient/GeneratedArtifacts
313 | **/*.DesktopClient/ModelManifest.xml
314 | **/*.Server/GeneratedArtifacts
315 | **/*.Server/ModelManifest.xml
316 | _Pvt_Extensions
317 |
318 | # Paket dependency manager
319 | .paket/paket.exe
320 | paket-files/
321 |
322 | # FAKE - F# Make
323 | .fake/
324 |
325 | # CodeRush personal settings
326 | .cr/personal
327 |
328 | # Python Tools for Visual Studio (PTVS)
329 | __pycache__/
330 | *.pyc
331 |
332 | # Cake - Uncomment if you are using it
333 | # tools/**
334 | # !tools/packages.config
335 |
336 | # Tabs Studio
337 | *.tss
338 |
339 | # Telerik's JustMock configuration file
340 | *.jmconfig
341 |
342 | # BizTalk build output
343 | *.btp.cs
344 | *.btm.cs
345 | *.odx.cs
346 | *.xsd.cs
347 |
348 | # OpenCover UI analysis results
349 | OpenCover/
350 |
351 | # Azure Stream Analytics local run output
352 | ASALocalRun/
353 |
354 | # MSBuild Binary and Structured Log
355 | *.binlog
356 |
357 | # NVidia Nsight GPU debugger configuration file
358 | *.nvuser
359 |
360 | # MFractors (Xamarin productivity tool) working folder
361 | .mfractor/
362 |
363 | # Local History for Visual Studio
364 | .localhistory/
365 |
366 | # Visual Studio History (VSHistory) files
367 | .vshistory/
368 |
369 | # BeatPulse healthcheck temp database
370 | healthchecksdb
371 |
372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
373 | MigrationBackup/
374 |
375 | # Ionide (cross platform F# VS Code tools) working folder
376 | .ionide/
377 |
378 | # Fody - auto-generated XML schema
379 | FodyWeavers.xsd
380 |
381 | # VS Code files for those working on multiple tools
382 | .vscode/*
383 | !.vscode/settings.json
384 | !.vscode/tasks.json
385 | !.vscode/launch.json
386 | !.vscode/extensions.json
387 | *.code-workspace
388 |
389 | # Local History for Visual Studio Code
390 | .history/
391 |
392 | # Windows Installer files from build outputs
393 | *.cab
394 | *.msi
395 | *.msix
396 | *.msm
397 | *.msp
398 |
399 | # JetBrains Rider
400 | *.sln.iml
401 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # SwarmFit 0.0.3
2 | _Published to NuGet on 2024-09-24_
3 | * SwarmFitter: Improved performance by minimizing allocations (#1) @neon-sunset
4 | * Renamed classes and arguments to favor `parameters` instead of `variables`
5 | * SwarmFitter: Use a customizable `IRandomNumberGenerator` with a default that maximizes performance
6 | * IFunction: Created an interface to help display documented common functions with known typical parameters
7 | * SwarmFit: Refactored to support successive iterative parameter improvements
8 | * QuickFit: Improved default parameters and end optimizing when successive iterations no longer improve the fit
9 |
10 | # SwarmFit 0.0.2
11 | _Published to NuGet on 2024-09-14_
12 | * Published to test automated deployment
13 |
14 | # SwarmFit 0.0.1
15 | _Published to NuGet on 2024-09-14_
16 | * Initial Release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Scott W Harden
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwarmFit
2 |
3 | [](https://github.com/swharden/SwarmFit/actions/workflows/ci.yaml)
4 |
5 | **SwarmFit is a .NET package for fitting curves to X/Y data** points using [particle swarm optimization](https://en.wikipedia.org/wiki/Particle_swarm_optimization). Unlike other gradient decent strategies, finding the derivative of the error function is not required. SwarmFit can be used to calculate best fit curves for arbitrary equations that use any number of parameters.
6 |
7 | 
8 |
9 | ## Quickstart
10 |
11 | ```cs
12 | // data points to fit
13 | double[] xs = [1, 2, 3, 4, 5];
14 | double[] ys = [304, 229, 174, 134, 111];
15 |
16 | // a custom function to fit to the data using any number of parameters
17 | static double MyFitFunc(double x, double[] parameters)
18 | {
19 | double a = parameters[0];
20 | double b = parameters[1];
21 | double c = parameters[2];
22 | return a + b * Math.Exp(x * c);
23 | }
24 |
25 | // the minimum and maximum value for each parameter
26 | double[] minVars = [-100, -5000, -10];
27 | double[] maxVars = [100, 5000, 10];
28 |
29 | // perform the fit with general purpose settings
30 | double[] solution = QuickFit.Solve(xs, ys, MyFitFunc, minVars, maxVars);
31 |
32 | // display the solution
33 | double a = solution[0];
34 | double b = solution[1];
35 | double c = solution[2];
36 | Console.WriteLine($"Y = {a} + {b} * e^(x * {c})");
37 | ```
38 |
39 | ## Graphical Demo
40 |
41 | A sample application is included with this repository which generates random data and fits it using common curve functions. This application demonstrates advanced features such as fitter fine-tuning, iterative error logging, charting, etc.
42 |
43 | 
--------------------------------------------------------------------------------
/dev/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/example1.png
--------------------------------------------------------------------------------
/dev/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/example2.png
--------------------------------------------------------------------------------
/dev/example3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/example3.png
--------------------------------------------------------------------------------
/dev/fit.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/fit.gif
--------------------------------------------------------------------------------
/dev/fit2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/fit2.gif
--------------------------------------------------------------------------------
/dev/icon/icon.bat:
--------------------------------------------------------------------------------
1 | ::magick convert icon.png -define icon:auto-resize=16,48,256 -compress zip icon.ico
2 | magick convert icon.png -define icon:auto-resize=48 -compress zip icon.ico
3 | pause
--------------------------------------------------------------------------------
/dev/icon/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/icon/icon.ico
--------------------------------------------------------------------------------
/dev/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/dev/icon/icon.png
--------------------------------------------------------------------------------
/dev/icon/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
209 |
--------------------------------------------------------------------------------
/src/SwarmFit.Benchmarks/Program.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Running;
2 |
3 | BenchmarkSwitcher
4 | .FromAssembly(typeof(Program).Assembly)
5 | .Run(args);
6 |
--------------------------------------------------------------------------------
/src/SwarmFit.Benchmarks/Simple.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 |
3 | namespace SwarmFit.Benchmarks;
4 |
5 | [ShortRunJob]
6 | [MemoryDiagnoser]
7 | public class Simple
8 | {
9 | [Benchmark]
10 | public void SolveDemo()
11 | {
12 | // data points to fit
13 | double[] xs = [1, 2, 3, 4, 5];
14 | double[] ys = [304, 229, 174, 134, 111];
15 |
16 | // a custom function to fit to the data using any number of parameters
17 | static double MyFitFunc(double x, double[] parameters)
18 | {
19 | double a = parameters[0];
20 | double b = parameters[1];
21 | double c = parameters[2];
22 | return a + b * Math.Exp(x * c);
23 | }
24 |
25 | // the minimum and maximum value for each parameter
26 | double[] minVars = [-100, -5000, -10];
27 | double[] maxVars = [100, 5000, 10];
28 |
29 | // perform the fit with general purpose settings
30 | double[] solution = QuickFit.Solve(xs, ys, MyFitFunc, minVars, maxVars);
31 |
32 | // display the solution
33 | double a = solution[0];
34 | double b = solution[1];
35 | double c = solution[2];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/SwarmFit.Benchmarks/SwarmFit.Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/SwarmFit.Benchmarks/run.bat:
--------------------------------------------------------------------------------
1 | dotnet run --configuration release --filter *
2 | pause
--------------------------------------------------------------------------------
/src/SwarmFit.Demo/Form1.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.Demo;
2 |
3 | partial class Form1
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | formsPlot1 = new ScottPlot.WinForms.FormsPlot();
32 | comboFormula = new ComboBox();
33 | btnRandomize = new Button();
34 | checkBox1 = new CheckBox();
35 | btnStep = new Button();
36 | btnSolve = new Button();
37 | richTextBox1 = new RichTextBox();
38 | btnImprove = new Button();
39 | btnZeroError = new Button();
40 | btnImproveStop = new Button();
41 | SuspendLayout();
42 | //
43 | // formsPlot1
44 | //
45 | formsPlot1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
46 | formsPlot1.DisplayScale = 1F;
47 | formsPlot1.Location = new Point(12, 166);
48 | formsPlot1.Name = "formsPlot1";
49 | formsPlot1.Size = new Size(804, 421);
50 | formsPlot1.TabIndex = 8;
51 | //
52 | // comboFormula
53 | //
54 | comboFormula.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
55 | comboFormula.DropDownStyle = ComboBoxStyle.DropDownList;
56 | comboFormula.FormattingEnabled = true;
57 | comboFormula.Location = new Point(12, 12);
58 | comboFormula.Name = "comboFormula";
59 | comboFormula.Size = new Size(804, 23);
60 | comboFormula.TabIndex = 9;
61 | //
62 | // btnRandomize
63 | //
64 | btnRandomize.Location = new Point(12, 41);
65 | btnRandomize.Name = "btnRandomize";
66 | btnRandomize.Size = new Size(97, 41);
67 | btnRandomize.TabIndex = 10;
68 | btnRandomize.Text = "Randomize";
69 | btnRandomize.UseVisualStyleBackColor = true;
70 | //
71 | // checkBox1
72 | //
73 | checkBox1.AutoSize = true;
74 | checkBox1.Location = new Point(115, 54);
75 | checkBox1.Name = "checkBox1";
76 | checkBox1.Size = new Size(56, 19);
77 | checkBox1.TabIndex = 14;
78 | checkBox1.Text = "Noise";
79 | checkBox1.UseVisualStyleBackColor = true;
80 | //
81 | // btnStep
82 | //
83 | btnStep.Location = new Point(177, 41);
84 | btnStep.Name = "btnStep";
85 | btnStep.Size = new Size(97, 41);
86 | btnStep.TabIndex = 15;
87 | btnStep.Text = "Iterate Once";
88 | btnStep.UseVisualStyleBackColor = true;
89 | //
90 | // btnSolve
91 | //
92 | btnSolve.Location = new Point(280, 41);
93 | btnSolve.Name = "btnSolve";
94 | btnSolve.Size = new Size(97, 41);
95 | btnSolve.TabIndex = 16;
96 | btnSolve.Text = "Iterate 100 Times";
97 | btnSolve.UseVisualStyleBackColor = true;
98 | //
99 | // richTextBox1
100 | //
101 | richTextBox1.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
102 | richTextBox1.BackColor = SystemColors.Control;
103 | richTextBox1.BorderStyle = BorderStyle.None;
104 | richTextBox1.Location = new Point(12, 88);
105 | richTextBox1.Name = "richTextBox1";
106 | richTextBox1.ReadOnly = true;
107 | richTextBox1.Size = new Size(804, 72);
108 | richTextBox1.TabIndex = 17;
109 | richTextBox1.Text = "";
110 | //
111 | // btnImprove
112 | //
113 | btnImprove.Location = new Point(383, 41);
114 | btnImprove.Name = "btnImprove";
115 | btnImprove.Size = new Size(97, 41);
116 | btnImprove.TabIndex = 18;
117 | btnImprove.Text = "Iterate Until Improved";
118 | btnImprove.UseVisualStyleBackColor = true;
119 | //
120 | // btnZeroError
121 | //
122 | btnZeroError.Location = new Point(486, 42);
123 | btnZeroError.Name = "btnZeroError";
124 | btnZeroError.Size = new Size(97, 41);
125 | btnZeroError.TabIndex = 19;
126 | btnZeroError.Text = "Iterate Until Zero Error";
127 | btnZeroError.UseVisualStyleBackColor = true;
128 | //
129 | // button1
130 | //
131 | btnImproveStop.Location = new Point(589, 42);
132 | btnImproveStop.Name = "button1";
133 | btnImproveStop.Size = new Size(129, 41);
134 | btnImproveStop.TabIndex = 20;
135 | btnImproveStop.Text = "Iterate Until Improvements Stop";
136 | btnImproveStop.UseVisualStyleBackColor = true;
137 | //
138 | // Form1
139 | //
140 | AutoScaleDimensions = new SizeF(7F, 15F);
141 | AutoScaleMode = AutoScaleMode.Font;
142 | ClientSize = new Size(828, 599);
143 | Controls.Add(btnImproveStop);
144 | Controls.Add(btnZeroError);
145 | Controls.Add(btnImprove);
146 | Controls.Add(richTextBox1);
147 | Controls.Add(btnSolve);
148 | Controls.Add(btnStep);
149 | Controls.Add(checkBox1);
150 | Controls.Add(btnRandomize);
151 | Controls.Add(comboFormula);
152 | Controls.Add(formsPlot1);
153 | Name = "Form1";
154 | StartPosition = FormStartPosition.CenterScreen;
155 | Text = "SwarmFit Demo";
156 | ResumeLayout(false);
157 | PerformLayout();
158 | }
159 |
160 | #endregion
161 | private ScottPlot.WinForms.FormsPlot formsPlot1;
162 | private ComboBox comboFormula;
163 | private Button btnRandomize;
164 | private CheckBox checkBox1;
165 | private Button btnStep;
166 | private Button btnSolve;
167 | private RichTextBox richTextBox1;
168 | private Button btnImprove;
169 | private Button btnZeroError;
170 | private Button btnImproveStop;
171 | }
--------------------------------------------------------------------------------
/src/SwarmFit.Demo/Form1.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.Demo;
2 |
3 | public partial class Form1 : Form
4 | {
5 | double[] Xs = [];
6 | double[] Ys = [];
7 |
8 | SwarmFitter? Fitter;
9 |
10 | readonly IFunction[] StandardFunctions = StandardFunction.GetAll();
11 |
12 | IFunction SelectedFunction => StandardFunctions[comboFormula.SelectedIndex];
13 |
14 | public Form1()
15 | {
16 | InitializeComponent();
17 | StandardFunctions.ToList().ForEach(x => comboFormula.Items.Add($"{x.Name} ({x.Formula()})"));
18 | comboFormula.SelectedIndex = 0;
19 | comboFormula.SelectedIndexChanged += (s, e) => Randomize();
20 | btnRandomize.Click += (s, e) => Randomize();
21 | btnStep.Click += (s, e) => Iterate(1);
22 | btnSolve.Click += (s, e) => Iterate(100);
23 | btnImprove.Click += (s, e) => IterateUntilImproved();
24 | btnZeroError.Click += (s, e) => IterateUntilNoError();
25 | btnImproveStop.Click += (s, e) => IterateUntilImprovementsStop();
26 | checkBox1.CheckedChanged += (s, e) => Randomize();
27 | Load += (s, e) => Randomize();
28 | }
29 |
30 | void Iterate(int count)
31 | {
32 | if (Fitter is null)
33 | return;
34 |
35 | FitSolution solution = Fitter.Solve(count);
36 | DisplayResult(solution);
37 | }
38 |
39 | void IterateUntilImproved()
40 | {
41 | if (Fitter is null)
42 | return;
43 |
44 | // iterate until a single improvement is achieved, but set the maximum limit anyway to
45 | // a very large number to prevent an infinite loop if a perfect solution has already been found.
46 | FitSolution solution = Fitter.Solve(maxIterations: 1_000_000, maxImprovements: 1);
47 | DisplayResult(solution);
48 | }
49 |
50 | void IterateUntilNoError()
51 | {
52 | if (Fitter is null)
53 | return;
54 |
55 | // iterate until a single improvement is achieved, but set the maximum limit anyway to
56 | // a very large number to prevent an infinite loop if a perfect solution has already been found.
57 | FitSolution solution = Fitter.Solve(maxIterations: 1_000_000, errorThreshold: 0);
58 | DisplayResult(solution);
59 | }
60 |
61 | void IterateUntilImprovementsStop()
62 | {
63 | if (Fitter is null)
64 | return;
65 |
66 | // iterate until a single improvement is achieved, but set the maximum limit anyway to
67 | // a very large number to prevent an infinite loop if a perfect solution has already been found.
68 | while (true)
69 | {
70 | int originalImprovements = Fitter.ImprovementCount;
71 | Fitter.Solve(maxIterations: 1000, errorThreshold: 0);
72 | if (Fitter.ImprovementCount == originalImprovements)
73 | {
74 | break;
75 | }
76 | }
77 |
78 | DisplayResult(Fitter.BestSolution);
79 | }
80 |
81 | void Randomize()
82 | {
83 | double[] parameters = SelectedFunction.TypicalParameterLimits.Select(x => x.Random(Random.Shared)).ToArray();
84 |
85 | int count = 10;
86 | Xs = ScottPlot.Generate.Range(SelectedFunction.TypicalXRange.min, SelectedFunction.TypicalXRange.max, n: count).ToArray();
87 | Ys = Xs.Select(x => SelectedFunction.Function.Invoke(x, parameters)).ToArray();
88 |
89 | if (checkBox1.Checked)
90 | {
91 | double ySpan = Ys.Max() - Ys.Min();
92 | double amount = 0.2;
93 | for (int i = 0; i < Ys.Length; i++)
94 | {
95 | Ys[i] += ySpan * amount * (Random.Shared.NextDouble() - .5) * 2;
96 | }
97 | }
98 |
99 | Plotting.PlotDataPoints(formsPlot1.Plot, Xs, Ys);
100 | formsPlot1.Plot.Title(SelectedFunction.Formula());
101 | formsPlot1.Refresh();
102 |
103 | Fitter = new(Xs, Ys, SelectedFunction.Function, SelectedFunction.TypicalParameterLimits);
104 | }
105 |
106 | void DisplayResult(FitSolution solution)
107 | {
108 | if (Fitter is null)
109 | return;
110 |
111 | richTextBox1.Text = $"Calculation time: {solution.Elapsed.TotalMilliseconds:N2} msec\n" +
112 | $"Iterations: {solution.Iterations:N0}\n" +
113 | $"Improvements: {solution.ImprovementCount:N0}\n" +
114 | $"Error: {solution.Error}";
115 |
116 | Plotting.PlotFitCurve(formsPlot1.Plot, SelectedFunction, solution);
117 | formsPlot1.Plot.Title(SelectedFunction.Formula(solution.Parameters));
118 | formsPlot1.Refresh();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/SwarmFit.Demo/Form1.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/src/SwarmFit.Demo/Plotting.cs:
--------------------------------------------------------------------------------
1 | using ScottPlot;
2 |
3 | namespace SwarmFit.Demo;
4 |
5 | internal static class Plotting
6 | {
7 | public static void PlotDataPoints(Plot plot, double[] xs, double[] ys)
8 | {
9 | plot.Clear();
10 | var pts = plot.Add.Markers(xs, ys);
11 | pts.MarkerSize = 10;
12 | plot.Axes.AutoScale();
13 | }
14 |
15 | public static void PlotFitCurve(Plot plot, IFunction function, FitSolution solution)
16 | {
17 | plot.Clear();
18 |
19 | double fitXMin = plot.Axes.Bottom.Min;
20 | double fitXMax = plot.Axes.Bottom.Max;
21 | double[] fitXs = Generate.Range(fitXMin, fitXMax, (fitXMax - fitXMin) / 100);
22 | double[] fitYs = fitXs.Select(x => function.Function.Invoke(x, solution.Parameters)).ToArray();
23 |
24 | var sl = plot.Add.ScatterLine(fitXs, fitYs);
25 | sl.LineWidth = 2;
26 | sl.Color = Colors.Black;
27 | sl.LinePattern = LinePattern.DenselyDashed;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SwarmFit.Demo/Program.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.Demo;
2 |
3 | static class Program
4 | {
5 | ///
6 | /// The main entry point for the application.
7 | ///
8 | [STAThread]
9 | static void Main()
10 | {
11 | // To customize application configuration such as set high DPI settings or default font,
12 | // see https://aka.ms/applicationconfiguration.
13 | ApplicationConfiguration.Initialize();
14 | Application.Run(new Form1());
15 | }
16 | }
--------------------------------------------------------------------------------
/src/SwarmFit.Demo/SwarmFit.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net8.0-windows
6 | enable
7 | true
8 | enable
9 | true
10 | NU1701
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/SwarmFit.Tests/Animation.cs:
--------------------------------------------------------------------------------
1 | using ScottPlot;
2 |
3 | namespace SwarmFit.Tests;
4 |
5 | public class Animation
6 | {
7 | [Ignore("only used for creating graphics for the website")]
8 | [Test]
9 | public void Test_Fit_Rainbow()
10 | {
11 |
12 | static double MyFunc(double x, double[] parameters)
13 | {
14 | double a = parameters[0];
15 | double b = parameters[1];
16 | double c = parameters[2];
17 | return a + b * Math.Exp(-x * c);
18 | }
19 |
20 | double[] parameters = [5, 7, .7];
21 | double[] xs = ScottPlot.Generate.Consecutive(10, first: 1);
22 | double[] ys = xs.Select(x => MyFunc(x, parameters)).ToArray();
23 | ScottPlot.Generate.AddNoiseInPlace(xs, .3);
24 | ScottPlot.Generate.AddNoiseInPlace(ys, .3);
25 |
26 | ParameterLimits[] limits = [
27 | new(0, 20),
28 | new(0, 20),
29 | new (.1, 10)
30 | ];
31 |
32 | SwarmFitter fitter = new(xs, ys, MyFunc, limits);
33 |
34 | Plot plot = new();
35 |
36 | var dataPoints = plot.Add.Markers(fitter.Xs, fitter.Ys);
37 | dataPoints.MarkerSize = 15;
38 | dataPoints.MarkerShape = MarkerShape.OpenCircle;
39 | dataPoints.MarkerLineWidth = 2;
40 | dataPoints.Color = Colors.Black;
41 |
42 | plot.Axes.AutoScale();
43 | plot.Axes.ZoomOut(1.5, 1.5);
44 | double[] fitXs = Generate.Range(plot.Axes.Bottom.Range.Min, plot.Axes.Bottom.Range.Max, plot.Axes.Bottom.Range.Span / 100);
45 | ScottPlot.Colormaps.Turbo cmap = new();
46 |
47 | int improvements = 150;
48 | for (int i = 0; i < improvements; i++)
49 | {
50 | FitSolution solution = fitter.Solve(maxImprovements: 1);
51 |
52 | double a = solution.Parameters[0];
53 | double b = solution.Parameters[1];
54 | double c = solution.Parameters[2];
55 | string formula = $"Y = {a:N3} + {b:N3} * e^(-x * {c:N3})";
56 | plot.Title(formula);
57 |
58 | plot.Clear();
59 | double[] fitYs = fitXs.Select(x => MyFunc(x, solution.Parameters)).ToArray();
60 | var tempLine = plot.Add.ScatterLine(fitXs, fitYs);
61 | tempLine.Color = cmap.GetColor(i, improvements, .2, 1).WithAlpha(.7);
62 | tempLine.LineWidth = 4;
63 |
64 | plot.SavePng($"animation/test-{i:0000}.png", 400, 300);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/SwarmFit.Tests/KnownSolutions.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 |
3 | namespace SwarmFit.Tests;
4 |
5 | public class KnownSolutions
6 | {
7 | [Test]
8 | public void Test_Fit_Values()
9 | {
10 | double[] parameters = [5, 7, 0.3];
11 | static double MyFunc(double x, double[] p) => p[0] + p[1] * Math.Exp(p[2] * x);
12 |
13 | double[] xs = [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
14 | double[] ys = xs.Select(x => MyFunc(x, parameters)).ToArray();
15 |
16 | // Define limits for each variable
17 | double[] minVars = [0, 0, 0];
18 | double[] maxVars = [10, 10, 1];
19 |
20 | // Find a solution for the best parameters to fit the curve to the data points
21 | double[] solution = QuickFit.Solve(xs, ys, MyFunc, minVars, maxVars);
22 |
23 | solution[0].Should().BeApproximately(parameters[0], 1e-5);
24 | solution[1].Should().BeApproximately(parameters[1], 1e-5);
25 | solution[2].Should().BeApproximately(parameters[2], 1e-5);
26 |
27 | Plotting.PlotFit(xs, ys, MyFunc, solution).SavePng("Test_Fit_Values.png", 400, 300);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SwarmFit.Tests/Plotting.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.Tests;
2 |
3 | internal static class Plotting
4 | {
5 | public static ScottPlot.Plot PlotFit(double[] xs, double[] ys, Func func, double[] solution)
6 | {
7 | ScottPlot.Plot plot = new();
8 |
9 | var marks = plot.Add.ScatterPoints(xs, ys);
10 | marks.MarkerSize = 10;
11 | plot.Axes.AutoScale();
12 |
13 | var limits = plot.Axes.GetLimits();
14 | double[] fitXs = ScottPlot.Generate.Range(limits.Left, limits.Right, limits.HorizontalSpan / 100);
15 | double[] fitYs = fitXs.Select(x => func(x, solution)).ToArray();
16 | var line = plot.Add.ScatterLine(fitXs, fitYs);
17 | line.LineWidth = 2;
18 |
19 | return plot;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/SwarmFit.Tests/Quickstart.cs:
--------------------------------------------------------------------------------
1 | using ScottPlot;
2 |
3 | namespace SwarmFit.Tests;
4 |
5 | public class Quickstart
6 | {
7 | [Test]
8 | public void Test_Fit_Quickstart()
9 | {
10 | // data points to fit
11 | double[] xs = [1, 2, 3, 4, 5];
12 | double[] ys = [304, 229, 174, 134, 111];
13 |
14 | // define a fit function using any number of variables.
15 | static double MyFunc(double x, double[] vars)
16 | {
17 | // Y = A + B * e^(x*C)
18 | return vars[0] + vars[1] * Math.Exp(vars[2] * x);
19 | }
20 |
21 | // define the minimum and maximum value for each variable
22 | double[] minVars = [-100, -5000, -10];
23 | double[] maxVars = [100, 5000, 10];
24 |
25 | // perform the fit
26 | double[] solution = QuickFit.Solve(xs, ys, MyFunc, minVars, maxVars);
27 |
28 | // display the solution
29 | double a = solution[0];
30 | double b = solution[1];
31 | double c = solution[2];
32 | Console.WriteLine($"Y = {a:N2} + {b:N2} * e^(x * {c:N2})");
33 | }
34 |
35 | [Test]
36 | public void Test_Logo()
37 | {
38 | double[] xs = [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
39 | double[] ys = [304.08994, 229.13878, 173.71886, 135.75499,
40 | 111.096794, 94.25109, 81.55578, 71.30187,
41 | 62.146603, 54.212032, 49.20715, 46.765743];
42 |
43 | static double Exponential3P(double x, double[] parameters)
44 | {
45 | return parameters[0] * Math.Exp(parameters[1] * x) + parameters[2];
46 | }
47 |
48 | ParameterLimits[] limits = [new(-5000, 5000), new(-100, 100), new(-100, 100)];
49 | SwarmFitter fitter = new(xs, ys, Exponential3P, limits);
50 | FitSolution solution = fitter.Solve(10_000);
51 |
52 | double[] fitYs = xs.Select(x => Exponential3P(x, solution.Parameters)).ToArray();
53 |
54 | Plot plot = new();
55 | var data = plot.Add.Markers(xs, ys);
56 | data.LegendText = "Data Points";
57 |
58 | var fit = plot.Add.ScatterLine(xs, fitYs);
59 | fit.LegendText = "Fitted Curve";
60 | fit.LineWidth = 2;
61 | fit.LinePattern = LinePattern.DenselyDashed;
62 | fit.LineColor = Colors.Black;
63 |
64 | plot.Legend.Alignment = Alignment.UpperRight;
65 |
66 | plot.Title($"Y = {solution.Parameters[2]:0.00} + {solution.Parameters[0]:0.00} * e^({solution.Parameters[1]:0.00} * x)");
67 | plot.Axes.Title.Label.Bold = false;
68 |
69 | var saved = plot.SavePng("test.png", 400, 300);
70 | Console.WriteLine(saved);
71 | //saved.LaunchInBrowser();
72 | }
73 | }
--------------------------------------------------------------------------------
/src/SwarmFit.Tests/RandomNumberGeneratorTests.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.Tests;
2 |
3 | class RandomNumberGeneratorTests
4 | {
5 | [Test]
6 | public void Test_SystemRandom_ValuesAreUnique()
7 | {
8 | // NOTE: a duplicate is found by luck after 66k iterations
9 | AssertNoDuplicates(new RandomNumberGenerators.SystemRandom(randomSeed: false), 50_000);
10 | }
11 |
12 | [Test]
13 | public void Test_XorShift_ValuesAreUnique()
14 | {
15 | AssertNoDuplicates(new RandomNumberGenerators.XorShift(), 100_000);
16 | }
17 |
18 | private static void AssertNoDuplicates(IRandomNumberGenerator RNG, int count)
19 | {
20 | HashSet seen = [];
21 | for (int i = 0; i < count; i++)
22 | {
23 | double value = RNG.NextDouble();
24 | if (seen.Contains(value))
25 | throw new InvalidOperationException($"duplicate number found after {i} iterations");
26 | seen.Add(value);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SwarmFit.Tests/SwarmFit.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 | all
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/SwarmFit.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.35122.118
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwarmFit", "SwarmFit\SwarmFit.csproj", "{3B3A28AA-135A-493E-99D9-CC5625D24346}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwarmFit.Demo", "SwarmFit.Demo\SwarmFit.Demo.csproj", "{C663E7B3-03C5-43B9-B92F-4FA9970B2DA8}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwarmFit.Tests", "SwarmFit.Tests\SwarmFit.Tests.csproj", "{5B93F4D3-B9F2-495A-830E-63CB96A3A699}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwarmFit.Benchmarks", "SwarmFit.Benchmarks\SwarmFit.Benchmarks.csproj", "{0F1DA296-6DFC-4C07-9172-2EAB50CA6A45}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {3B3A28AA-135A-493E-99D9-CC5625D24346}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {3B3A28AA-135A-493E-99D9-CC5625D24346}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {3B3A28AA-135A-493E-99D9-CC5625D24346}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {3B3A28AA-135A-493E-99D9-CC5625D24346}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {C663E7B3-03C5-43B9-B92F-4FA9970B2DA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {C663E7B3-03C5-43B9-B92F-4FA9970B2DA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {C663E7B3-03C5-43B9-B92F-4FA9970B2DA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {C663E7B3-03C5-43B9-B92F-4FA9970B2DA8}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {5B93F4D3-B9F2-495A-830E-63CB96A3A699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {5B93F4D3-B9F2-495A-830E-63CB96A3A699}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {5B93F4D3-B9F2-495A-830E-63CB96A3A699}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {5B93F4D3-B9F2-495A-830E-63CB96A3A699}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {0F1DA296-6DFC-4C07-9172-2EAB50CA6A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {0F1DA296-6DFC-4C07-9172-2EAB50CA6A45}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {0F1DA296-6DFC-4C07-9172-2EAB50CA6A45}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {0F1DA296-6DFC-4C07-9172-2EAB50CA6A45}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {E45BA962-1CFA-4982-9EF1-FAB43E624A03}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/src/SwarmFit/Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public static class Extensions
4 | {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/src/SwarmFit/FitSolution.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public class FitSolution(double[] parameters, double error, TimeSpan elapsed, int iterations, int improvements)
4 | {
5 | public double[] Parameters { get; } = [.. parameters];
6 | public double Error { get; } = error;
7 | public TimeSpan Elapsed { get; } = elapsed;
8 | public int Iterations { get; } = iterations;
9 | public int ImprovementCount { get; } = improvements;
10 | };
--------------------------------------------------------------------------------
/src/SwarmFit/IFunction.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | ///
4 | /// Classes implement this to pair a function with information that describes it
5 | ///
6 | public interface IFunction
7 | {
8 | ///
9 | /// Common name for the formula
10 | ///
11 | string Name { get; }
12 |
13 | ///
14 | /// Text description of the formula (e.g., Y = m * X + b)
15 | ///
16 | string Formula();
17 |
18 | ///
19 | /// Text description of the formula using the given parameters
20 | ///
21 | string Formula(double[] parameters, int precision = 3);
22 |
23 | ///
24 | /// Number of parameters to use when calling
25 | ///
26 | int ParameterCount { get; }
27 |
28 | ///
29 | /// Typical parameter ranges that may be used to evaluate the shape of the curve
30 | ///
31 | public ParameterLimits[] TypicalParameterLimits { get; }
32 |
33 | ///
34 | /// Typical horizontal range used to evaluate the shape of the curve
35 | ///
36 | public (double min, double max) TypicalXRange { get; }
37 |
38 | ///
39 | /// The function that returns a Y given an X and a collection of parameters (with a length equal to )
40 | ///
41 | Func Function { get; }
42 | }
43 |
--------------------------------------------------------------------------------
/src/SwarmFit/IRandomNumberGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public interface IRandomNumberGenerator
4 | {
5 | ///
6 | /// Return a random double between 0 and 1
7 | ///
8 | public double NextDouble();
9 | }
10 |
--------------------------------------------------------------------------------
/src/SwarmFit/ParameterLimits.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace SwarmFit;
4 |
5 | public readonly struct ParameterLimits(double min, double max)
6 | {
7 | public double Min { get; } = Math.Min(min, max);
8 | public double Max { get; } = Math.Max(min, max);
9 | public double Mid => (Min + Max) / 2;
10 | public double Span => Max - Min;
11 | public double Random(IRandomNumberGenerator rand) => Span * rand.NextDouble() + Min;
12 | public double Random(Random rand) => Span * rand.NextDouble() + Min;
13 |
14 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
15 | public double Clamp(double value)
16 | {
17 | if (value < Min) return Min;
18 | else if (value > Max) return Max;
19 | else return value;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/SwarmFit/Particle.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public class Particle
4 | {
5 | public double[] Positions;
6 | public double[] BestPositions;
7 | public double[] Velocities;
8 |
9 | public double Error;
10 | public double BestError;
11 |
12 | public Particle(double[] pos, double err, double[] vel, double[] bestPos, double bestErr)
13 | {
14 | Positions = new double[pos.Length];
15 | pos.CopyTo(Positions, 0);
16 | Error = err;
17 | Velocities = new double[vel.Length];
18 | vel.CopyTo(Velocities, 0);
19 | BestPositions = new double[bestPos.Length];
20 | bestPos.CopyTo(BestPositions, 0);
21 | BestError = bestErr;
22 | }
23 |
24 | public void RandomizePositions(IRandomNumberGenerator rand, ParameterLimits[] limits)
25 | {
26 | double[] positions = Positions;
27 | for (int i = 0; i < positions.Length; i++)
28 | {
29 | positions[i] = limits[i].Random(rand);
30 | }
31 | positions.AsSpan().CopyTo(BestPositions);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/SwarmFit/QuickFit.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public static class QuickFit
4 | {
5 | ///
6 | /// This function provides a simple API for fitting parameters of a function to find the best for for a collection of X/Y data points.
7 | /// The fitter will attempt continue to optimize parameters until 500 iterations yields no further improvement.
8 | /// The swarm fitter has many configuration options which are not available when calling this function, so advanced users
9 | /// are encouraged to instantiate a and interact it with directly to find the ideal solution.
10 | ///
11 | /// horizontal data values to fit
12 | /// vertical data values to fit
13 | /// a user defined function which calculates Y given X according to a collection of parameters
14 | /// minimum possible value for each parameter
15 | /// maximum possible value for each parameter
16 | /// Number of particles to use for fitting
17 | /// Fitting will be aborted after this number of total iterations is performed
18 | /// optimized parameters for the curve
19 | ///
20 | public static double[] Solve(double[] xs, double[] ys, Func func, double[] parameterMins, double[] parameterMaxes, int numParticles = 10, int maxIterations = 10_000)
21 | {
22 | if (parameterMins.Length != parameterMaxes.Length)
23 | {
24 | throw new ArgumentException($"{nameof(parameterMins)} and {nameof(parameterMaxes)} must have equal length");
25 | }
26 |
27 | ParameterLimits[] limits = Enumerable
28 | .Range(0, parameterMins.Length)
29 | .Select(x => new ParameterLimits(parameterMins[x], parameterMaxes[x]))
30 | .ToArray();
31 |
32 | SwarmFitter fitter = new(xs, ys, func, limits, numParticles);
33 |
34 | while (fitter.IterationCount < maxIterations)
35 | {
36 | int originalImprovementCount = fitter.ImprovementCount;
37 | FitSolution solution = fitter.Solve(maxIterations: 500);
38 | if (fitter.ImprovementCount == originalImprovementCount)
39 | break;
40 | }
41 |
42 | return fitter.BestSolution.Parameters;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/SwarmFit/RandomNumberGenerators/CryptoRandom.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.RandomNumberGenerators;
2 |
3 | public class CruptoRandom : IRandomNumberGenerator
4 | {
5 | private readonly System.Security.Cryptography.RandomNumberGenerator Rand = System.Security.Cryptography.RandomNumberGenerator.Create();
6 |
7 | readonly byte[] bytes = new byte[sizeof(int)];
8 |
9 | public double NextDouble()
10 | {
11 | Rand.GetBytes(bytes);
12 | return BitConverter.ToInt32(bytes, 0) & (int.MaxValue - 1);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/SwarmFit/RandomNumberGenerators/SystemRandom.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.RandomNumberGenerators;
2 |
3 | public class SystemRandom(bool randomSeed = false) : IRandomNumberGenerator
4 | {
5 | private readonly Random Rand = new(randomSeed ? GetRandomSeed() : 0);
6 |
7 | private static int GetRandomSeed()
8 | {
9 | System.Security.Cryptography.RandomNumberGenerator RNG = System.Security.Cryptography.RandomNumberGenerator.Create();
10 | byte[] data = new byte[sizeof(int)];
11 | RNG.GetBytes(data);
12 | return BitConverter.ToInt32(data, 0) & (int.MaxValue - 1);
13 | }
14 |
15 | public double NextDouble()
16 | {
17 | return Rand.NextDouble();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/SwarmFit/RandomNumberGenerators/XorShift.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.RandomNumberGenerators;
2 |
3 | // See: https://en.wikipedia.org/wiki/Xorshift
4 | // Note: 2463534242 in binary is 10010010110101101000110010100010 which has a nice balance of 1s and 0s
5 | public class XorShift(uint seed = 2463534242) : IRandomNumberGenerator
6 | {
7 | private uint Rand = seed;
8 |
9 | public uint Next()
10 | {
11 | Rand ^= Rand << 13;
12 | Rand ^= Rand >> 17;
13 | Rand ^= Rand << 5;
14 | return Rand;
15 | }
16 |
17 | public double NextDouble()
18 | {
19 | return (double)Next() / uint.MaxValue;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/SwarmFit/SampleData.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public static class SampleData
4 | {
5 | public static double[] Range(double min, double max, double delta)
6 | {
7 | int count = (int)((max - min) / delta) + 1;
8 | double[] result = new double[count];
9 |
10 | for (int i = 0; i < count; i++)
11 | {
12 | result[i] = min + i * delta;
13 | }
14 |
15 | return result;
16 | }
17 |
18 | public static double[] Exponential(double[] xs, double a, double b, double c)
19 | {
20 | double[] ys = new double[xs.Length];
21 |
22 | for (int i = 0; i < xs.Length; i++)
23 | {
24 | ys[i] = a + b * Math.Exp(xs[i] * c);
25 | }
26 |
27 | return ys;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SwarmFit/StandardFunction.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit;
2 |
3 | public static class StandardFunction
4 | {
5 | public static IFunction[] GetAll()
6 | {
7 | return System.Reflection.Assembly.GetExecutingAssembly()
8 | .GetTypes()
9 | .Where(x => x.IsClass)
10 | .Where(x => !x.IsAbstract)
11 | .Where(x => x.GetInterfaces().Contains(typeof(IFunction)))
12 | .Where(x => x.GetConstructors().Where(x => x.GetParameters().Length == 0).Any())
13 | .Select(x => (IFunction)Activator.CreateInstance(x)!)
14 | .ToArray();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/SwarmFit/StandardFunctions/ExpDec1.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.StandardFunctions;
2 |
3 | // https://www.originlab.com/doc/en/Origin-Help/ExpDec1-FitFunc
4 | public class ExpDec1 : IFunction
5 | {
6 | public string Name => "One-phase exponential decay function with time constant parameter";
7 | public string Formula() => "Y = y0 + a * Exp(-x/t)";
8 | public string Formula(double[] parameters, int precision = 3)
9 | {
10 | double y0 = Math.Round(parameters[0], precision);
11 | double a = Math.Round(parameters[1], precision);
12 | double t = Math.Round(parameters[2], precision);
13 | return $"Y = {y0} + {a} * Exp(-x/{t})";
14 | }
15 |
16 | public int ParameterCount => 3;
17 | public (double min, double max) TypicalXRange => (1, 10);
18 | public Func Function { get; } = (double x, double[] parameters) =>
19 | {
20 | double y0 = parameters[0];
21 | double a = parameters[1];
22 | double t = parameters[2];
23 | return y0 + a + Math.Exp(-x / t);
24 | };
25 |
26 | public ParameterLimits[] TypicalParameterLimits => [
27 | new ParameterLimits(0, 10), // y0
28 | new ParameterLimits(1, 10), // a
29 | new ParameterLimits(.1, 5), // t
30 | ];
31 | }
32 |
--------------------------------------------------------------------------------
/src/SwarmFit/StandardFunctions/ExpDecay1.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.StandardFunctions;
2 |
3 | // https://www.originlab.com/doc/en/Origin-Help/ExpDecay1-FitFunc
4 | public class ExpDecay1 : IFunction
5 | {
6 | public string Name => "One-phase exponential decay function with time offset";
7 | public string Formula() => "Y = y0 + a * Exp(-(x-x0)/t)";
8 | public string Formula(double[] parameters, int precision = 3)
9 | {
10 | double y0 = Math.Round(parameters[0], precision);
11 | double a = Math.Round(parameters[1], precision);
12 | double x0 = Math.Round(parameters[2], precision);
13 | double t = Math.Round(parameters[3], precision);
14 | return $"Y = {y0} + {a} * Exp(-(x-{x0})/{t})";
15 | }
16 |
17 | public int ParameterCount => 4;
18 | public (double min, double max) TypicalXRange => (1, 10);
19 | public Func Function { get; } = (double x, double[] parameters) =>
20 | {
21 | double y0 = parameters[0];
22 | double a = parameters[1];
23 | double x0 = parameters[0];
24 | double t = parameters[1];
25 | return y0 + a + Math.Exp(-(x - x0) / t);
26 | };
27 |
28 | public ParameterLimits[] TypicalParameterLimits => [
29 | new ParameterLimits(0, 10), // y0
30 | new ParameterLimits(1, 10), // a
31 | new ParameterLimits(1, 10), // x0
32 | new ParameterLimits(.1, 5), // t
33 | ];
34 | }
35 |
--------------------------------------------------------------------------------
/src/SwarmFit/StandardFunctions/Linear.cs:
--------------------------------------------------------------------------------
1 | namespace SwarmFit.StandardFunctions;
2 |
3 | public class Linear : IFunction
4 | {
5 | public string Name => "Linear";
6 | public string Formula() => "Y=m*X+b";
7 | public string Formula(double[] p, int precision = 3)
8 | {
9 | double m = Math.Round(p[0], precision);
10 | double b = Math.Round(p[1], precision);
11 | return $"Y={m}*X+{b}";
12 | }
13 |
14 | public int ParameterCount => 2;
15 | public (double min, double max) TypicalXRange => (-10, 10);
16 | public Func Function { get; } = (double x, double[] parameters) =>
17 | {
18 | double m = parameters[0];
19 | double b = parameters[1];
20 | return m * x * + b;
21 | };
22 | public ParameterLimits[] TypicalParameterLimits => [
23 | new(-10, 10), // m
24 | new(-10, 10), // b
25 | ];
26 | }
27 |
--------------------------------------------------------------------------------
/src/SwarmFit/SwarmFit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net8.0
5 | enable
6 | enable
7 | 12
8 | SwarmFit
9 | 0.0.3
10 | MIT
11 | Scott Harden
12 | Harden Technologies, LLC
13 | Copyright 2024 (c) Scott Harden / Harden Technologies, LLC
14 | A .NET standard library for fitting curves to data using particle swarm optimization
15 | curve fit fitting curve-fit curve-fitting math equation function
16 | https://github.com/swharden/SwarmFit
17 | https://github.com/swharden/SwarmFit.git
18 | git
19 | icon.png
20 | readme.md
21 | true
22 | snupkg
23 | true
24 | true
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | all
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/SwarmFit/SwarmFitter.cs:
--------------------------------------------------------------------------------
1 | /* Adapted from "Particle Swarm Optimization Using C#" by James McCaffrey,
2 | * published in Visual Studio Magazine on 11/25/2013 and other texts:
3 | * https://visualstudiomagazine.com/Articles/2013/11/01/Particle-Swarm-Optimization.aspx
4 | * https://ieeexplore.ieee.org/document/9376550
5 | * https://www.mathworks.com/matlabcentral/fileexchange/58895-ppso?s_tid=blogs_rc_5
6 | */
7 |
8 | using System.Diagnostics;
9 | using System.Runtime.CompilerServices;
10 |
11 | namespace SwarmFit;
12 |
13 | public class SwarmFitter
14 | {
15 | ///
16 | /// X values (provided by the user) that the parameters of will be optimized to fit.
17 | ///
18 | public double[] Xs { get; }
19 |
20 | ///
21 | /// Y values (provided by the user) that the parameters of will be optimized to fit
22 | ///
23 | public double[] Ys { get; }
24 |
25 | ///
26 | /// The function (provided by the user) that parameters will be optimized for to fit and
27 | ///
28 | public Func Function { get; }
29 |
30 | ///
31 | /// Bounds for each parameter used when randomizing particle positions.
32 | ///
33 | private ParameterLimits[] ParameterLimits { get; }
34 |
35 | ///
36 | /// Number of parameters to be optimized.
37 | /// This must be consistent with the size of and the
38 | /// length of the parameter array consumes.
39 | ///
40 | public int ParameterCount => ParameterLimits.Length;
41 |
42 | ///
43 | /// Source of randomness used for randomizing particle placement and inertial angle
44 | ///
45 | public IRandomNumberGenerator Rand { get; set; } = new RandomNumberGenerators.XorShift();
46 |
47 |
48 | public double InertiaWeight { get; set; } = 0.729;
49 | public double LocalWeight { get; set; } = 1.49445;
50 | public double GlobalWeight { get; set; } = 1.49445;
51 |
52 | ///
53 | /// Fractional chance that a particle will be killed and randomized after an iteration
54 | ///
55 | public double ParticleDeathProbability = 0.01;
56 |
57 | ///
58 | /// Number of particles in the field
59 | ///
60 | public int ParticleCount => Particles.Length;
61 |
62 | private Particle[] Particles { get; }
63 | double[] BestParameters { get; }
64 | double BestError = double.MaxValue;
65 |
66 | readonly Stopwatch Stopwatch = new();
67 |
68 | ///
69 | /// Total time the solver was running
70 | ///
71 | public TimeSpan CalculationTime => Stopwatch.Elapsed;
72 |
73 | ///
74 | /// Total number of best fit curves identified including the present one
75 | ///
76 | public int ImprovementCount { get; private set; } = 0;
77 |
78 | ///
79 | /// Total number of times the entire particle field was iterated forward
80 | ///
81 | public int IterationCount { get; private set; } = 0;
82 |
83 | public FitSolution BestSolution => new(BestParameters, BestError, CalculationTime, IterationCount, ImprovementCount);
84 |
85 | public SwarmFitter(double[] xs, double[] ys, Func func, ParameterLimits[] limits, int numParticles = 5)
86 | {
87 | if (xs.Length != ys.Length)
88 | throw new ArgumentException($"{nameof(xs)} and {nameof(ys)} must have equal length");
89 |
90 | Xs = xs;
91 | Ys = ys;
92 | Function = func;
93 | ParameterLimits = limits;
94 | Particles = new Particle[numParticles];
95 | BestParameters = new double[ParameterCount];
96 | }
97 |
98 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
99 | double GetError(double[] parameters)
100 | {
101 | double error = 0;
102 | double[] xs = Xs;
103 | double[] ys = Ys;
104 |
105 | for (int i = 0; i < xs.Length; i++)
106 | {
107 | double predictedY = Function(xs[i], parameters);
108 | double actualY = ys[i];
109 | double diff = Math.Abs(predictedY - actualY);
110 | error += diff;
111 | }
112 |
113 | return error;
114 | }
115 |
116 | ///
117 | /// Progress toward an ideal solution.
118 | /// This method may be called multiple times.
119 | ///
120 | /// Stop trying to improve the fit if this number of particle progressions has been reached
121 | /// Stop trying to improve the fit if this number of additional improvements has been made
122 | /// Stop trying to improve the fit if the total error no longer exceeds this value
123 | public FitSolution Solve(int maxIterations = 1000, int maxImprovements = int.MaxValue, float errorThreshold = 0)
124 | {
125 | Stopwatch.Start();
126 |
127 | // Randomize all particles only if this is the first time running the solver.
128 | // This way the solve method may be called multiple times to improve the solution.
129 | if (IterationCount == 0)
130 | {
131 | RandomizeAll();
132 | }
133 |
134 | int improvementLimit = maxImprovements == int.MaxValue ? maxImprovements : ImprovementCount + maxImprovements;
135 |
136 | for (int i = 0; i < maxIterations; i++)
137 | {
138 | IterateAllParticles();
139 | if (ImprovementCount >= improvementLimit || BestError <= errorThreshold)
140 | break;
141 | }
142 |
143 | Stopwatch.Stop();
144 |
145 | return BestSolution;
146 | }
147 |
148 | private void RandomizeAll()
149 | {
150 | double initialVelocityRandomness = 0.01;
151 | for (int i = 0; i < Particles.Length; i++)
152 | {
153 | double[] randomPositions = ParameterLimits.Select(x => x.Random(Rand)).ToArray();
154 | double error = GetError(randomPositions);
155 | double[] randomVelocities = ParameterLimits.Select(x => x.Random(Rand) * x.Span * initialVelocityRandomness).ToArray();
156 | Particles[i] = new Particle(randomPositions, error, randomVelocities, randomPositions, error);
157 |
158 | if (Particles[i].Error < BestError)
159 | {
160 | BestError = Particles[i].Error;
161 | Particles[i].Positions.AsSpan().CopyTo(BestParameters);
162 | }
163 | }
164 | }
165 |
166 | private void IterateAllParticles()
167 | {
168 | double originalBestError = BestError;
169 |
170 | foreach (Particle particle in Particles)
171 | {
172 | IterateParticle(particle);
173 | }
174 |
175 | IterationCount++;
176 | if (BestError != originalBestError)
177 | {
178 | ImprovementCount++;
179 | }
180 | }
181 |
182 | private void IterateParticle(Particle particle)
183 | {
184 | double[] positions = particle.Positions;
185 | double[] bestPositions = particle.BestPositions;
186 | double[] velocities = particle.Velocities;
187 |
188 | for (int j = 0; j < velocities.Length; j++)
189 | {
190 | double inertia = InertiaWeight * velocities[j];
191 | double local = LocalWeight * Rand.NextDouble() * (bestPositions[j] - positions[j]);
192 | double global = GlobalWeight * Rand.NextDouble() * (BestParameters[j] - positions[j]);
193 | velocities[j] = inertia + local + global;
194 | }
195 |
196 | for (int j = 0; j < positions.Length; j++)
197 | {
198 | positions[j] = positions[j] + velocities[j];
199 | positions[j] = ParameterLimits[j].Clamp(positions[j]);
200 | }
201 |
202 | particle.Error = GetError(positions);
203 |
204 | if (particle.Error < particle.BestError)
205 | {
206 | positions.AsSpan().CopyTo(bestPositions);
207 | particle.BestError = particle.Error;
208 | }
209 |
210 | if (particle.Error < BestError)
211 | {
212 | positions.AsSpan().CopyTo(BestParameters);
213 | BestError = particle.BestError;
214 | }
215 |
216 | bool isBestParticle = particle.BestError == BestError;
217 |
218 | if (!isBestParticle && (Rand.NextDouble() < ParticleDeathProbability))
219 | {
220 | particle.RandomizePositions(Rand, ParameterLimits);
221 | particle.Error = GetError(particle.Positions);
222 | particle.BestError = particle.Error;
223 |
224 | if (particle.Error < BestError)
225 | {
226 | BestError = particle.Error;
227 | positions.AsSpan().CopyTo(BestParameters);
228 | }
229 | }
230 | }
231 | }
--------------------------------------------------------------------------------
/src/SwarmFit/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/SwarmFit/60fcc85184b405ef65de551261d6cd89f2d73b46/src/SwarmFit/icon.png
--------------------------------------------------------------------------------
/src/SwarmFit/nuget-readme.md:
--------------------------------------------------------------------------------
1 | # SwarmFit
2 |
3 | **SwarmFit is a .NET package for fitting curves to X/Y data** points using particle swarm optimization. Unlike other gradient decent strategies, finding the derivative of the error function is not required. SwarmFit can be used to calculate best fit curves for arbitrary equations that use any number of parameters.
4 |
5 | 
6 |
7 | ## Quickstart
8 |
9 | ```cs
10 | // data points to fit
11 | double[] xs = [1, 2, 3, 4, 5];
12 | double[] ys = [304, 229, 174, 134, 111];
13 |
14 | // define a fit function using any number of parameters
15 | static double MyFunc(double x, double[] parameters)
16 | {
17 | double a = parameters[0];
18 | double b = parameters[1];
19 | double c = parameters[2];
20 | return a + b * Math.Exp(x * c);
21 | }
22 |
23 | // define the minimum and maximum value for each parameter
24 | double[] paramMins = [-100, -5000, -10];
25 | double[] paramMaxs = [100, 5000, 10];
26 |
27 | // perform the fit
28 | double[] solution = QuickFit.Solve(xs, ys, MyFunc, minParams, maxParams);
29 |
30 | // display the solution
31 | double a = solution[0];
32 | double b = solution[1];
33 | double c = solution[2];
34 | Console.WriteLine($"Y = {a} + {b} * e^(x * {c})");
35 | ```
36 |
37 | ## Documentation
38 |
39 | See [https://github.com/swharden/SwarmFit/](https://github.com/swharden/SwarmFit/) for additional code and documentation
--------------------------------------------------------------------------------