├── .github └── workflows │ ├── build-and-test.yml │ ├── format-and-lint.yml │ └── release.yml ├── .gitignore ├── .release-please-manifest.json ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── codecov.yml ├── release-please-config.json ├── renovate.json5 ├── scripts ├── coverage.sh └── setup.sh └── src ├── gaussian.rs ├── gaussian ├── deprecated.rs ├── error.rs ├── gaussian.rs └── operations.rs ├── lib.rs ├── linalg.rs └── main.rs /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_and_test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | version: 12 | - stable 13 | - beta 14 | - nightly 15 | name: ${{ matrix.version }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Cache rustup directory 20 | uses: actions/cache@v4 21 | env: 22 | cache-name: cache-rustup 23 | with: 24 | path: | 25 | ~/.rustup/toolchains 26 | ~/.rustup/update-hashes 27 | ~/.rustup/settings.toml 28 | key: v1-${{ runner.os }}-build-and-test-${{ env.cache-name }}-${{ matrix.version }}-${{ github.sha }} 29 | restore-keys: | 30 | v1-${{ runner.os }}-build-and-test-${{ env.cache-name }}-${{ matrix.version }}- 31 | 32 | - name: Switch toolchain version 33 | run: rustup default ${{ matrix.version }} 34 | 35 | - name: Set nightly flags (nightly only) 36 | if: ${{ matrix.version == 'nightly' }} 37 | run: | 38 | echo "RUSTFLAGS=-Cinstrument-coverage" >> $GITHUB_ENV 39 | echo "LLVM_PROFILE_FILE=tasshi-me-%p-%m.profraw" >> $GITHUB_ENV 40 | 41 | - name: Rustup version 42 | run: rustup --version 43 | 44 | - name: Cargo version 45 | run: cargo --version 46 | 47 | - name: Cache cargo directory 48 | uses: actions/cache@v4 49 | env: 50 | cache-name: cache-cargo 51 | with: 52 | path: | 53 | ~/.cargo/bin 54 | ~/.cargo/env 55 | ~/.cargo/registry 56 | target 57 | key: v1-${{ runner.os }}-build-and-test-${{ env.cache-name }}-${{ matrix.version }}-${{ github.sha }} 58 | restore-keys: | 59 | v1-${{ runner.os }}-build-and-test-${{ env.cache-name }}-${{ matrix.version }}- 60 | 61 | - name: Check cached directories (pre build) 62 | run: | 63 | ls -la ~/.rustup/toolchains || echo 64 | ls -la ~/.rustup/update-hashes || echo 65 | ls -la ~/.rustup/settings.toml || echo 66 | ls -la ~/.cargo/ || echo 67 | ls -la ~/.cargo/bin || echo 68 | ls -la ~/.cargo/env || echo 69 | ls -la ~/.cargo/registry || echo 70 | ls -la target || echo 71 | 72 | - name: Install grcov (nightly only) 73 | if: ${{ matrix.version == 'nightly' }} 74 | run: | 75 | rustup component add llvm-tools-preview 76 | curl -L https://github.com/mozilla/grcov/releases/download/v0.8.11/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - 77 | 78 | - name: Clean 79 | run: cargo clean 80 | 81 | - name: Build 82 | run: cargo build --verbose 83 | 84 | - name: Test 85 | run: cargo test --verbose 86 | 87 | - name: Coverage (nightly only) 88 | if: ${{ matrix.version == 'nightly' }} 89 | run: ./grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o lcov.info 90 | 91 | - name: Upload coverage report (nightly only) 92 | if: ${{ matrix.version == 'nightly' }} 93 | run: bash <(curl -s https://codecov.io/bash) -f lcov.info 94 | 95 | - name: Check cached directories (post build) 96 | run: | 97 | ls -la ~/.rustup/toolchains || echo 98 | ls -la ~/.rustup/update-hashes || echo 99 | ls -la ~/.rustup/settings.toml || echo 100 | ls -la ~/.cargo/ || echo 101 | ls -la ~/.cargo/bin || echo 102 | ls -la ~/.cargo/env || echo 103 | ls -la ~/.cargo/registry || echo 104 | ls -la target || echo 105 | -------------------------------------------------------------------------------- /.github/workflows/format-and-lint.yml: -------------------------------------------------------------------------------- 1 | name: Format and Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | format: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Rustup version 11 | run: rustup --version 12 | - name: Cargo version 13 | run: cargo --version 14 | - name: Install toolchains 15 | run: rustup component add rustfmt 16 | - name: Check format 17 | run: | 18 | cargo fmt --version 19 | cargo fmt -- --check 20 | lint: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Rustup version 25 | run: rustup --version 26 | - name: Cargo version 27 | run: cargo --version 28 | - name: Install toolchains 29 | run: rustup component add clippy 30 | - name: Lint 31 | run: | 32 | cargo clippy --version 33 | cargo clippy 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release and Publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | release: 9 | outputs: 10 | release_created: ${{ steps.release.outputs.release_created }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: google-github-actions/release-please-action@cc61a07e2da466bebbc19b3a7dd01d6aecb20d1e # v4 14 | id: release 15 | 16 | publish: 17 | runs-on: ubuntu-latest 18 | needs: release 19 | if: ${{ needs.release.outputs.release_created }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Rustup version 23 | run: rustup --version 24 | - name: Cargo version 25 | run: cargo --version 26 | - name: Publish 27 | run: cargo publish 28 | env: 29 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | /.idea 6 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "0.5.1" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.5.1](https://github.com/tasshi-me/fitting-rs/compare/0.5.0...0.5.1) (2024-02-26) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **deps:** update rust crate approx to 0.5.1 ([#26](https://github.com/tasshi-me/fitting-rs/issues/26)) ([dccb0e4](https://github.com/tasshi-me/fitting-rs/commit/dccb0e4aec293b8dc758c89936e0acbed119e476)) 9 | 10 | 11 | ### Chores 12 | 13 | * **deps:** pin google-github-actions/release-please-action action to e0b9d18 ([#58](https://github.com/tasshi-me/fitting-rs/issues/58)) ([3e470d0](https://github.com/tasshi-me/fitting-rs/commit/3e470d05a3be8c4242ad74efaf034aeae778edd4)) 14 | * **deps:** update actions/cache action to v4 ([#93](https://github.com/tasshi-me/fitting-rs/issues/93)) ([2503fdc](https://github.com/tasshi-me/fitting-rs/commit/2503fdc2ccdd6b1eea08287e73f1f0b1728257c4)) 15 | * **deps:** update actions/checkout action to v4 ([#89](https://github.com/tasshi-me/fitting-rs/issues/89)) ([bf940b7](https://github.com/tasshi-me/fitting-rs/commit/bf940b7e726b7a985fd5474449aee5661b31503b)) 16 | * **deps:** update google-github-actions/release-please-action action to v4 ([#92](https://github.com/tasshi-me/fitting-rs/issues/92)) ([b04cb5d](https://github.com/tasshi-me/fitting-rs/commit/b04cb5dbc151faca189a2833e4314acdd75ec6ea)) 17 | * **deps:** update rust crate serde to 1.0.158 ([#59](https://github.com/tasshi-me/fitting-rs/issues/59)) ([fc140a2](https://github.com/tasshi-me/fitting-rs/commit/fc140a29180e38fe11d469613be9736a2f8cf7fe)) 18 | * **deps:** update rust crate serde to 1.0.159 ([#60](https://github.com/tasshi-me/fitting-rs/issues/60)) ([c5e26a2](https://github.com/tasshi-me/fitting-rs/commit/c5e26a2e3607922584e395f7688070592c59bd90)) 19 | * **deps:** update rust crate serde to 1.0.160 ([#62](https://github.com/tasshi-me/fitting-rs/issues/62)) ([7843c44](https://github.com/tasshi-me/fitting-rs/commit/7843c44c64c35367ef82edf8c6bbcf0897dcc485)) 20 | * **deps:** update rust crate serde to 1.0.167 ([#63](https://github.com/tasshi-me/fitting-rs/issues/63)) ([5705d00](https://github.com/tasshi-me/fitting-rs/commit/5705d0007f48f628e2be7e80da60fa63c6012c53)) 21 | * **deps:** update rust crate serde to 1.0.168 ([#65](https://github.com/tasshi-me/fitting-rs/issues/65)) ([63b4e72](https://github.com/tasshi-me/fitting-rs/commit/63b4e72be6eaf47508e4e15ba919a4435c5e9952)) 22 | * **deps:** update rust crate serde to 1.0.169 ([#66](https://github.com/tasshi-me/fitting-rs/issues/66)) ([8e08dc0](https://github.com/tasshi-me/fitting-rs/commit/8e08dc0b431ee71442cd0cc490775cc85f5ddf64)) 23 | * **deps:** update rust crate serde to 1.0.171 ([#67](https://github.com/tasshi-me/fitting-rs/issues/67)) ([e04e6ce](https://github.com/tasshi-me/fitting-rs/commit/e04e6ce9bec2663b0a99368c7844c4e460489d0e)) 24 | * **deps:** update rust crate serde to 1.0.173 ([#68](https://github.com/tasshi-me/fitting-rs/issues/68)) ([d2228d1](https://github.com/tasshi-me/fitting-rs/commit/d2228d1073ba80f1c1cfdf41b60f03f1ab7230f9)) 25 | * **deps:** update rust crate serde to 1.0.174 ([#69](https://github.com/tasshi-me/fitting-rs/issues/69)) ([bb78f29](https://github.com/tasshi-me/fitting-rs/commit/bb78f29539e519a95dfbc4e5718553fee987c89b)) 26 | * **deps:** update rust crate serde to 1.0.175 ([#71](https://github.com/tasshi-me/fitting-rs/issues/71)) ([fab1b35](https://github.com/tasshi-me/fitting-rs/commit/fab1b356ccfadc36fe0e35f42c0251aed7b37082)) 27 | * **deps:** update rust crate serde to 1.0.176 ([#73](https://github.com/tasshi-me/fitting-rs/issues/73)) ([b1735ab](https://github.com/tasshi-me/fitting-rs/commit/b1735abae6af6413207d0e4f445ae708648646f0)) 28 | * **deps:** update rust crate serde to 1.0.177 ([#74](https://github.com/tasshi-me/fitting-rs/issues/74)) ([ef0c8e8](https://github.com/tasshi-me/fitting-rs/commit/ef0c8e83c398fdb0aeb749930c47bf43e74ef54c)) 29 | * **deps:** update rust crate serde to 1.0.178 ([#75](https://github.com/tasshi-me/fitting-rs/issues/75)) ([16355f8](https://github.com/tasshi-me/fitting-rs/commit/16355f864daa31f14be17faf0a6b4bc899cc9365)) 30 | * **deps:** update rust crate serde to 1.0.179 ([#76](https://github.com/tasshi-me/fitting-rs/issues/76)) ([0559720](https://github.com/tasshi-me/fitting-rs/commit/05597205f3f3d04578a64f82ec5e14272fb1aaf6)) 31 | * **deps:** update rust crate serde to 1.0.180 ([#77](https://github.com/tasshi-me/fitting-rs/issues/77)) ([2b4976e](https://github.com/tasshi-me/fitting-rs/commit/2b4976e35a902f560c2dd667b9534b8f05208ddd)) 32 | * **deps:** update rust crate serde to 1.0.181 ([#78](https://github.com/tasshi-me/fitting-rs/issues/78)) ([e1de1b4](https://github.com/tasshi-me/fitting-rs/commit/e1de1b4058c18635f37f16e31a53d217dce6497c)) 33 | * **deps:** update rust crate serde to 1.0.182 ([#79](https://github.com/tasshi-me/fitting-rs/issues/79)) ([ae202c0](https://github.com/tasshi-me/fitting-rs/commit/ae202c03e5eb2e30a03adebb522c1ac7df762fed)) 34 | * **deps:** update rust crate serde to 1.0.183 ([#80](https://github.com/tasshi-me/fitting-rs/issues/80)) ([1c37d61](https://github.com/tasshi-me/fitting-rs/commit/1c37d61a37999d35b2610a033cd7714d8afa42cd)) 35 | * **deps:** update rust crate serde to 1.0.185 ([#84](https://github.com/tasshi-me/fitting-rs/issues/84)) ([5e622e4](https://github.com/tasshi-me/fitting-rs/commit/5e622e45ba7f126eda776d002f8e6013f727b53e)) 36 | * **deps:** update rust crate serde to 1.0.186 ([#85](https://github.com/tasshi-me/fitting-rs/issues/85)) ([168d144](https://github.com/tasshi-me/fitting-rs/commit/168d14422a2fdcb7739c673cb5e76a2b39544654)) 37 | * **deps:** update rust crate serde to 1.0.187 ([#86](https://github.com/tasshi-me/fitting-rs/issues/86)) ([52a5f76](https://github.com/tasshi-me/fitting-rs/commit/52a5f76c51ea48c908c15958f58f5a9a517705ec)) 38 | * **deps:** update rust crate serde to 1.0.188 ([#87](https://github.com/tasshi-me/fitting-rs/issues/87)) ([180867c](https://github.com/tasshi-me/fitting-rs/commit/180867c14b37b5e93b988214498bef22141a8b2f)) 39 | * **deps:** update rust crate serde to 1.0.197 ([#91](https://github.com/tasshi-me/fitting-rs/issues/91)) ([364fdf3](https://github.com/tasshi-me/fitting-rs/commit/364fdf30febe91017a28944466817bfebdd5a850)) 40 | * **deps:** update rust crate thiserror to 1.0.40 ([#57](https://github.com/tasshi-me/fitting-rs/issues/57)) ([59aa7f8](https://github.com/tasshi-me/fitting-rs/commit/59aa7f8a8c6234d56faeb12be7533c8567f51221)) 41 | * **deps:** update rust crate thiserror to 1.0.43 ([#64](https://github.com/tasshi-me/fitting-rs/issues/64)) ([323cbfc](https://github.com/tasshi-me/fitting-rs/commit/323cbfc2766a3477914e40b90da98accf2ec334b)) 42 | * **deps:** update rust crate thiserror to 1.0.44 ([#70](https://github.com/tasshi-me/fitting-rs/issues/70)) ([b449713](https://github.com/tasshi-me/fitting-rs/commit/b4497133a16cf0c89f177ee38fb7e37bdcde9337)) 43 | * **deps:** update rust crate thiserror to 1.0.45 ([#81](https://github.com/tasshi-me/fitting-rs/issues/81)) ([99c9110](https://github.com/tasshi-me/fitting-rs/commit/99c9110a18acda3c736260e75540b8f007f94c44)) 44 | * **deps:** update rust crate thiserror to 1.0.46 ([#82](https://github.com/tasshi-me/fitting-rs/issues/82)) ([caffc95](https://github.com/tasshi-me/fitting-rs/commit/caffc95ceda3abd0e85cf9d8ea347e144df99f1f)) 45 | * **deps:** update rust crate thiserror to 1.0.47 ([#83](https://github.com/tasshi-me/fitting-rs/issues/83)) ([5a0b847](https://github.com/tasshi-me/fitting-rs/commit/5a0b847ac6aae716e25d5378b3bfd918baf5f2d8)) 46 | * **deps:** update rust crate thiserror to 1.0.48 ([#88](https://github.com/tasshi-me/fitting-rs/issues/88)) ([8079ccd](https://github.com/tasshi-me/fitting-rs/commit/8079ccd0fd7b725d5bfbd88346aa6bfb04743baa)) 47 | * **deps:** update rust crate thiserror to 1.0.57 ([#90](https://github.com/tasshi-me/fitting-rs/issues/90)) ([05f2913](https://github.com/tasshi-me/fitting-rs/commit/05f29136a56a598df5c7d0228a70d80483f183dc)) 48 | * update repository owner ([26d95c3](https://github.com/tasshi-me/fitting-rs/commit/26d95c36f482e85531e28976a95dbeb46bbdfde4)) 49 | 50 | ## [0.5.0](https://github.com/tasshi-me/fitting-rs/compare/0.4.4...0.5.0) (2022-12-29) 51 | 52 | 53 | ### ⚠ BREAKING CHANGES 54 | 55 | * return an error if vec_y contains negative value ([#52](https://github.com/tasshi-me/fitting-rs/issues/52)) 56 | 57 | ### Bug Fixes 58 | 59 | * return an error if vec_y contains negative value ([#52](https://github.com/tasshi-me/fitting-rs/issues/52)) ([93b4019](https://github.com/tasshi-me/fitting-rs/commit/93b40195a92af9df5f457bde817ed41a97ae8adf)) 60 | 61 | ## [0.4.4](https://github.com/tasshi-me/fitting-rs/compare/0.4.3...0.4.4) (2022-12-29) 62 | 63 | 64 | ### Chores 65 | 66 | * add limitations section of `Gaussian::fit` ([#53](https://github.com/tasshi-me/fitting-rs/issues/53)) ([b556725](https://github.com/tasshi-me/fitting-rs/commit/b556725171b539dcaf2adc61e5cd40b6f5346b0d)) 67 | * replace deprecated method of `ndarray::Zip` ([700fcf5](https://github.com/tasshi-me/fitting-rs/commit/700fcf5570e910d8ee25cf9d272be28dc6afdd72)) 68 | 69 | ## [0.4.3](https://github.com/tasshi-me/fitting-rs/compare/0.4.2...0.4.3) (2022-12-26) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * **deps:** update rust crate serde to 1.0.148 ([#45](https://github.com/tasshi-me/fitting-rs/issues/45)) ([2f8a832](https://github.com/tasshi-me/fitting-rs/commit/2f8a8320d0f940984d65b6fe4da24433cba698eb)) 75 | * **deps:** update rust crate serde to 1.0.149 ([#47](https://github.com/tasshi-me/fitting-rs/issues/47)) ([317a8a5](https://github.com/tasshi-me/fitting-rs/commit/317a8a588babf0959be692384c3219821cbe2433)) 76 | * **deps:** update rust crate serde to 1.0.150 ([#48](https://github.com/tasshi-me/fitting-rs/issues/48)) ([ab34e27](https://github.com/tasshi-me/fitting-rs/commit/ab34e2758d619c9f6dd6c946f14474df8b780408)) 77 | * **deps:** update rust crate serde to 1.0.151 ([#49](https://github.com/tasshi-me/fitting-rs/issues/49)) ([f721ee0](https://github.com/tasshi-me/fitting-rs/commit/f721ee0329342f69ccd5ef1d58cba13e584171e0)) 78 | * **deps:** update rust crate serde to 1.0.152 ([#51](https://github.com/tasshi-me/fitting-rs/issues/51)) ([b52b737](https://github.com/tasshi-me/fitting-rs/commit/b52b73752adeb372654adeeed356a33633e381d9)) 79 | * **deps:** update rust crate thiserror to 1.0.38 ([#50](https://github.com/tasshi-me/fitting-rs/issues/50)) ([406f4ba](https://github.com/tasshi-me/fitting-rs/commit/406f4ba0b74165daa0ed7430f47861c8f3f0e81f)) 80 | 81 | ## [0.4.2](https://github.com/tasshi-me/fitting-rs/compare/0.4.1...0.4.2) (2022-10-21) 82 | 83 | 84 | ### Bug Fixes 85 | 86 | * **deps:** update rust crate serde to 1.0.145 ([#40](https://github.com/tasshi-me/fitting-rs/issues/40)) ([28b4d3d](https://github.com/tasshi-me/fitting-rs/commit/28b4d3d6dedd5153a84bb73d28efa2bb6ec800b4)) 87 | * **deps:** update rust crate serde to 1.0.146 ([#43](https://github.com/tasshi-me/fitting-rs/issues/43)) ([b104977](https://github.com/tasshi-me/fitting-rs/commit/b10497758766b9d9e4755dfb2d309dff3b9fa0fa)) 88 | * **deps:** update rust crate serde to 1.0.147 ([#44](https://github.com/tasshi-me/fitting-rs/issues/44)) ([13189bb](https://github.com/tasshi-me/fitting-rs/commit/13189bbf1b8e1e2dd0bd119066757e185e9b2684)) 89 | * **deps:** update rust crate thiserror to 1.0.35 ([#38](https://github.com/tasshi-me/fitting-rs/issues/38)) ([8f8e210](https://github.com/tasshi-me/fitting-rs/commit/8f8e210c087364a56c13c386ab65b861a84591f4)) 90 | * **deps:** update rust crate thiserror to 1.0.36 ([#41](https://github.com/tasshi-me/fitting-rs/issues/41)) ([a8563dc](https://github.com/tasshi-me/fitting-rs/commit/a8563dce301cc778db4d8968b54612bb6255fb11)) 91 | * **deps:** update rust crate thiserror to 1.0.37 ([#42](https://github.com/tasshi-me/fitting-rs/issues/42)) ([f14d758](https://github.com/tasshi-me/fitting-rs/commit/f14d758586b9a0b49f4e2d47a442bc3ee09ec5c9)) 92 | 93 | ## [0.4.1](https://github.com/tasshi-me/fitting-rs/compare/0.4.0...0.4.1) (2022-09-08) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * **deps:** update rust crate ndarray to 0.15.6 ([#27](https://github.com/tasshi-me/fitting-rs/issues/27)) ([ef30236](https://github.com/tasshi-me/fitting-rs/commit/ef30236fa7a5513b14b5d3ecd49fdca8aae74497)) 99 | * **deps:** update rust crate serde to 1.0.143 ([#31](https://github.com/tasshi-me/fitting-rs/issues/31)) ([4563832](https://github.com/tasshi-me/fitting-rs/commit/45638320e0ab7f6b1f5c0c2b5f021be988c4a5be)) 100 | * **deps:** update rust crate serde to 1.0.144 ([#37](https://github.com/tasshi-me/fitting-rs/issues/37)) ([2655092](https://github.com/tasshi-me/fitting-rs/commit/26550921439f3e189f0ce43767f4444a05f1c6b3)) 101 | * **deps:** update rust crate thiserror to 1.0.32 ([#25](https://github.com/tasshi-me/fitting-rs/issues/25)) ([eac8cbf](https://github.com/tasshi-me/fitting-rs/commit/eac8cbfdc2ab76db77b22ec2f8fa0c9741178956)) 102 | * **deps:** update rust crate thiserror to 1.0.34 ([#36](https://github.com/tasshi-me/fitting-rs/issues/36)) ([6fb6db0](https://github.com/tasshi-me/fitting-rs/commit/6fb6db00be8e56c52226f0c97aa2c796fd06a520)) 103 | 104 | 105 | ### Chores 106 | 107 | * update renovate.json5 ([557c972](https://github.com/tasshi-me/fitting-rs/commit/557c9720ef9b7f40999e6439ac91a65787c88486)) 108 | 109 | ## [0.4.0](https://github.com/tasshi-me/fitting-rs/compare/0.3.0...0.4.0) (2022-08-19) 110 | 111 | 112 | ### ⚠ BREAKING CHANGES 113 | 114 | * **gaussian:** Redesign API (#15) 115 | 116 | ### Features 117 | 118 | * **gaussian:** Redesign API ([#15](https://github.com/tasshi-me/fitting-rs/issues/15)) ([14c9340](https://github.com/tasshi-me/fitting-rs/commit/14c9340b046c3e47086ae685705acb72faf25a50)) 119 | 120 | 121 | ### Bug Fixes 122 | 123 | * coverage using cargo-kcov ([d1ce724](https://github.com/tasshi-me/fitting-rs/commit/d1ce724c8482288ca4e98b0bf30b76531cec505a)) 124 | 125 | 126 | ### Chores 127 | 128 | * add CHANGELOG.md ([adefab3](https://github.com/tasshi-me/fitting-rs/commit/adefab34cd8171e54d37172ebeba8cccf93b13f7)) 129 | * bootstrap releases for path: . ([#32](https://github.com/tasshi-me/fitting-rs/issues/32)) ([7096e8c](https://github.com/tasshi-me/fitting-rs/commit/7096e8c4aa13e7c66980df713b34cc9e8a4e5b43)) 130 | * **deps:** update actions/cache action to v3 ([#28](https://github.com/tasshi-me/fitting-rs/issues/28)) ([aae4a4b](https://github.com/tasshi-me/fitting-rs/commit/aae4a4bf3c2e4bfa4c49a6acbeb4119e8f0c2b5b)) 131 | * **deps:** update actions/checkout action to v3 ([#29](https://github.com/tasshi-me/fitting-rs/issues/29)) ([896bf16](https://github.com/tasshi-me/fitting-rs/commit/896bf16d27812a98b1e55a252ba6ae34c25c1921)) 132 | * updated Cargo.toml (exclude section) ([fe52125](https://github.com/tasshi-me/fitting-rs/commit/fe52125da9fd3312a4053b9d2a47c864a238d56d)) 133 | * updated README.md ([7422682](https://github.com/tasshi-me/fitting-rs/commit/7422682a6c08f57e191c037fdacf0554ec52de4f)) 134 | 135 | ## [0.3.0](https://github.com/tasshi-me/fitting-rs/compare/0.2.1...0.3.0) (2020-05-16) 136 | 137 | - Migrate from the `failure` crate to `thiserror`. 138 | - https://crates.io/crates/thiserror 139 | - Refactor some tests. 140 | 141 | 142 | ## [0.2.1](https://github.com/tasshi-me/fitting-rs/compare/0.2.0...0.2.1) (2019-12-04) 143 | 144 | - Error handing changed. Some functions returns Result instead of Option. 145 | - linalg.solve() is improved. Now it can solve NxM array with pivoting. 146 | 147 | 148 | ## [0.2.0](https://github.com/tasshi-me/fitting-rs/compare/0.1.0...0.2.0) (2019-11-08) 149 | 150 | - Using [ndarray](https://crates.io/crates/ndarray) instead of nested Vec 151 | - Improvement of unit test 152 | - Add status badges 153 | 154 | 155 | ## 0.1.0 (2019-11-08) 156 | 157 | - Implements linalg solve and gaussian fit. 158 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fitting" 3 | version = "0.5.1" 4 | authors = ["Masaharu TASHIRO "] 5 | edition = "2018" 6 | # A short blurb about the package. This is not rendered in any format when 7 | # uploaded to crates.io (aka this is not markdown). 8 | description = "Pure Rust curve fitting library" 9 | # These URLs point to more information about the package. These are 10 | # intended to be webviews of the relevant data, not necessarily compatible 11 | # with VCS tools and the like. 12 | documentation = "https://docs.rs/fitting/" 13 | homepage = "https://crates.io/crates/fitting" 14 | repository = "https://github.com/tasshi-me/fitting-rs" 15 | # This points to a file under the package root (relative to this `Cargo.toml`). 16 | # The contents of this file are stored and indexed in the registry. 17 | # crates.io will render this file and place the result on the crate's page. 18 | readme = "README.md" 19 | # This is a list of up to five keywords that describe this crate. Keywords 20 | # are searchable on crates.io, and you may choose any words that would 21 | # help someone find this crate. 22 | keywords = ["fitting", "statistics", "probability", "distribution", "math"] 23 | # This is a list of up to five categories where this crate would fit. 24 | # Categories are a fixed list available at crates.io/category_slugs, and 25 | # they must match exactly. 26 | categories = ["science"] 27 | # This is an SPDX 2.1 license expression for this package. Currently 28 | # crates.io will validate the license provided against a whitelist of 29 | # known license and exception identifiers from the SPDX license list 30 | # 2.4. Parentheses are not currently supported. 31 | # 32 | # Multiple licenses can be separated with a `/`, although that usage 33 | # is deprecated. Instead, use a license expression with AND and OR 34 | # operators to get more explicit semantics. 35 | license = "MIT" 36 | # If a package is using a nonstandard license, then this key may be specified in 37 | # lieu of the above key and must point to a file relative to this manifest 38 | # (similar to the readme key). 39 | # license-file = "..." 40 | exclude = ["/.github/*", "/scripts"] 41 | 42 | [badges] 43 | circle-ci = { repository = "tasshi-me/fitting-rs", branch = "master" } 44 | travis-ci = { repository = "tasshi-me/fitting-rs", branch = "master" } 45 | # Codecov: `repository` is required. `branch` is optional; default is `master` 46 | # `service` is optional; valid values are `github` (default), `bitbucket`, and 47 | # `gitlab`. 48 | codecov = { repository = "tasshi-me/fitting-rs", branch = "master", service = "github" } 49 | # Maintenance: `status` is required. Available options are: 50 | # - `actively-developed`: New features are being added and bugs are being fixed. 51 | # - `passively-maintained`: There are no plans for new features, but the maintainer intends to 52 | # respond to issues that get filed. 53 | # - `as-is`: The crate is feature complete, the maintainer does not intend to continue working on 54 | # it or providing support, but it works for the purposes it was designed for. 55 | # - `experimental`: The author wants to share it with the community but is not intending to meet 56 | # anyone's particular use case. 57 | # - `looking-for-maintainer`: The current maintainer would like to transfer the crate to someone 58 | # else. 59 | # - `deprecated`: The maintainer does not recommend using this crate (the description of the crate 60 | # can describe why, there could be a better solution available or there could be problems with 61 | # the crate that the author does not want to fix). 62 | # - `none`: Displays no badge on crates.io, since the maintainer has not chosen to specify 63 | # their intentions, potential crate users will need to investigate on their own. 64 | maintenance = { status = "actively-developed" } 65 | 66 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 67 | [dependencies] 68 | ndarray = { version = "0.15.6", features = ["serde", "approx", "approx-0_5"] } 69 | approx = "0.5.1" 70 | thiserror = "1.0.69" 71 | serde = { version = "1.0.219", features = ["derive"] } 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Masaharu TASHIRO 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 | # fitting-rs 2 | 3 | [![crates.io](https://img.shields.io/crates/v/fitting.svg)](https://crates.io/crates/fitting) 4 | [![docs.rs](https://docs.rs/fitting/badge.svg)](https://docs.rs/fitting) 5 | [![Build and Test](https://github.com/tasshi-me/fitting-rs/workflows/Build%20and%20Test/badge.svg)](https://github.com/tasshi-me/fitting-rs/actions?query=workflow%3A%22Build+and+Test%22) 6 | [![Format and Lint](https://github.com/tasshi-me/fitting-rs/workflows/Format%20and%20Lint/badge.svg)](https://github.com/tasshi-me/fitting-rs/actions?query=workflow%3A%22Format+and+Lint%22) 7 | [![codecov](https://codecov.io/gh/tasshi-me/fitting-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/tasshi-me/fitting-rs) 8 | 9 | Curve fitting library for Rust 10 | 11 | ## Updates 12 | 13 | See [CHANGELOG.md](./CHANGELOG.md) 14 | 15 | ## License 16 | 17 | This project is licensed under the MIT license. 18 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "src/gaussian/deprecated.rs" 3 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap-sha": "eeb0df82e4f1b425837bd05247cff12c7017bb51", 3 | "packages": { 4 | ".": { 5 | "changelog-path": "CHANGELOG.md", 6 | "release-type": "rust", 7 | "bump-minor-pre-major": true, 8 | "bump-patch-for-minor-pre-major": false, 9 | "draft": false, 10 | "prerelease": false, 11 | "include-component-in-tag": false, 12 | "include-v-in-tag": false, 13 | "changelog-sections": [ 14 | { 15 | "type": "feat", 16 | "section": "Features", 17 | "hidden": false 18 | }, 19 | { 20 | "type": "fix", 21 | "section": "Bug Fixes", 22 | "hidden": false 23 | }, 24 | { 25 | "type": "chore", 26 | "section": "Chores", 27 | "hidden":false 28 | } 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>tasshi-me/renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | export RUSTFLAGS="-Zinstrument-coverage" 3 | 4 | export TARGET_BINARY_DIR="target/debug" 5 | export COVERAGE_HTML_DIR=${TARGET_BINARY_DIR}"/coverage" 6 | export LLVM_PROFILE_DIR=${COVERAGE_HTML_DIR}"/profraw" 7 | export LLVM_PROFILE_FILE=${LLVM_PROFILE_DIR}"/tasshi-me-%p-%m.profraw" 8 | export LLVM_PROFDATA_FILE=${LLVM_PROFILE_DIR}"/merged.profdata" 9 | 10 | export TARGET_BINARY_FILE=${TARGET_BINARY_DIR}"/fitting" 11 | 12 | rm -rf ${LLVM_PROFILE_DIR} 13 | rm -rf ${COVERAGE_HTML_DIR} 14 | mkdir -p ${LLVM_PROFILE_DIR} 15 | mkdir -p ${COVERAGE_HTML_DIR} 16 | 17 | cargo +nightly clean --verbose 18 | cargo +nightly build --verbose 19 | cargo +nightly run --verbose 20 | cargo +nightly test --verbose 21 | 22 | # html 23 | rustup run nightly grcov . --binary-path ${TARGET_BINARY_DIR} -s . -t html --branch --ignore-not-existing --ignore "/*" -o ${COVERAGE_HTML_DIR} 24 | 25 | # stdout 26 | cargo +nightly profdata -- merge -sparse ${LLVM_PROFILE_DIR}/* -o ${LLVM_PROFDATA_FILE} 27 | cargo +nightly cov -- show ${TARGET_BINARY_FILE} \ 28 | -use-color \ 29 | -Xdemangler=rustfilt \ 30 | -instr-profile=${LLVM_PROFDATA_FILE} \ 31 | -show-line-counts-or-regions \ 32 | -show-instantiations \ 33 | --ignore-filename-regex="(.cargo|rustc)" 34 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | rustup toolchain install nightly 3 | rustup run nightly rustup component add llvm-tools-preview 4 | cargo +nightly install cargo-binutils 5 | cargo +nightly install grcov 6 | cargo +nightly install rustfilt 7 | -------------------------------------------------------------------------------- /src/gaussian.rs: -------------------------------------------------------------------------------- 1 | mod deprecated; 2 | mod error; 3 | mod gaussian; 4 | mod operations; 5 | 6 | #[doc(inline)] 7 | pub use self::deprecated::*; 8 | 9 | #[doc(inline)] 10 | pub use self::gaussian::Gaussian; 11 | 12 | #[doc(inline)] 13 | pub use self::error::GaussianError; 14 | -------------------------------------------------------------------------------- /src/gaussian/deprecated.rs: -------------------------------------------------------------------------------- 1 | use crate::gaussian::operations; 2 | use crate::gaussian::GaussianError; 3 | use crate::linalg::Float; 4 | use ndarray::Array1; 5 | 6 | /// Returns a value of gaussian function. 7 | /// 8 | /// # Examples 9 | /// Returns a value of gaussian function. 10 | /// 11 | /// ``` 12 | /// use fitting::gaussian; 13 | /// 14 | /// let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 15 | /// let x = 5.; 16 | /// let y = gaussian::val(x, mu, sigma, a); 17 | /// assert_eq!(y, a); 18 | /// 19 | /// ``` 20 | /// 21 | /// # Examples 22 | /// Returns the values of gaussian function. 23 | /// 24 | /// ``` 25 | /// use fitting::approx::assert_abs_diff_eq; 26 | /// use fitting::gaussian; 27 | /// use fitting::ndarray::{array, Array, Array1}; 28 | /// 29 | /// let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 30 | /// let x_vec: Array1 = Array::range(1., 10., 1.); 31 | /// let y_vec: Array1 = x_vec.iter().map(|x| gaussian::val(*x, mu, sigma, a)).collect(); 32 | /// let expected_ans = array![ 33 | /// 0.41111229050718745, 34 | /// 0.6065306597126334, 35 | /// 0.8007374029168081, 36 | /// 0.9459594689067654, 37 | /// 1., 38 | /// 0.9459594689067654, 39 | /// 0.8007374029168081, 40 | /// 0.6065306597126334, 41 | /// 0.41111229050718745 42 | /// ]; 43 | /// assert_abs_diff_eq!(&y_vec, &expected_ans, epsilon = 1e-9); 44 | /// ``` 45 | #[deprecated( 46 | since = "0.4.0", 47 | note = "Please use the Gaussian::val function instead" 48 | )] 49 | pub fn val(x: F, mu: F, sigma: F, a: F) -> F { 50 | operations::value(x, mu, sigma, a) 51 | } 52 | 53 | /// Estimates the parameters of gaussian function for generic data. 54 | /// The return value is `(mu, sigma, a)` 55 | /// 56 | /// This function implements the [Guos Algorithm](https://www.researchgate.net/publication/281907940_Comparison_of_Algorithms_For_Fitting_a_Gaussian_Function_Used_in_Testing_Smart_Sensors). 57 | /// 58 | /// # Examples 59 | /// Estimates the parameters for sample data. 60 | /// 61 | /// ``` 62 | /// use fitting::approx::assert_abs_diff_eq; 63 | /// use fitting::gaussian; 64 | /// use fitting::ndarray::{array, Array, Array1}; 65 | /// 66 | /// let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 67 | /// let x_vec: Array1 = Array::range(1., 10., 1.); 68 | /// let y_vec: Array1 = x_vec.iter().map(|x| gaussian::val(*x, mu, sigma, a)).collect(); 69 | /// let estimated = gaussian::fit(x_vec, y_vec).unwrap(); 70 | /// assert_abs_diff_eq!( 71 | /// &array![estimated.0, estimated.1, estimated.2], 72 | /// &array![mu, sigma, a], 73 | /// epsilon = 1e-9 74 | /// ); 75 | /// ``` 76 | /// 77 | /// # References 78 | /// \[1\] [E. Pastuchov ́a and M. Z ́akopˇcan, ”Comparison of Algorithms for Fitting a Gaussian Function used in Testing Smart Sensors”, Journal of Electrical Engineering, vol. 66, no. 3, pp. 178-181, 2015.](https://www.researchgate.net/publication/281907940_Comparison_of_Algorithms_For_Fitting_a_Gaussian_Function_Used_in_Testing_Smart_Sensors) 79 | #[deprecated( 80 | since = "0.4.0", 81 | note = "Please use the Gaussian::fit function instead" 82 | )] 83 | pub fn fit(x_vec: Array1, y_vec: Array1) -> Result<(F, F, F), GaussianError> { 84 | operations::fitting_guos(x_vec, y_vec) 85 | } 86 | -------------------------------------------------------------------------------- /src/gaussian/error.rs: -------------------------------------------------------------------------------- 1 | use crate::linalg::LinalgError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug, Eq, PartialEq)] 5 | pub enum GaussianError { 6 | /// Given y_vec contains a negative value 7 | #[error("Given y_vec contains a negative value")] 8 | GivenYVecContainsNegativeValue, 9 | /// Given y_vec contains a negative value 10 | #[error("Given x_vec has no element")] 11 | /// Given x_vec has no element 12 | GivenXVecHasNoElement, 13 | /// Error from [`crate::linalg::LinalgError`] 14 | #[error("Linalg error: {0:?}")] 15 | Linalg(#[from] LinalgError), 16 | } 17 | -------------------------------------------------------------------------------- /src/gaussian/gaussian.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use crate::gaussian::{operations, GaussianError}; 4 | use crate::linalg::Float; 5 | use approx::{AbsDiffEq, RelativeEq, UlpsEq}; 6 | use ndarray::{array, Array1}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// Gaussian Distribution 10 | #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Debug, Default, Serialize, Deserialize)] 11 | pub struct Gaussian { 12 | mu: F, 13 | sigma: F, 14 | a: F, 15 | } 16 | 17 | impl Gaussian { 18 | /// Create a new `Gaussian` with given parameters. 19 | pub fn new(mu: F, sigma: F, a: F) -> Gaussian { 20 | Gaussian { mu, sigma, a } 21 | } 22 | 23 | /// Return a reference to `mu`. 24 | pub fn mu(&self) -> &F { 25 | &self.mu 26 | } 27 | 28 | /// Return a mutable reference to `mu`. 29 | pub fn mu_mut(&mut self) -> &mut F { 30 | &mut self.mu 31 | } 32 | 33 | /// Return a reference to `sigma`. 34 | pub fn sigma(&self) -> &F { 35 | &self.sigma 36 | } 37 | 38 | /// Return a mutable reference to `sigma`. 39 | pub fn sigma_mut(&mut self) -> &mut F { 40 | &mut self.sigma 41 | } 42 | 43 | /// Return a reference to `a`. 44 | pub fn a(&self) -> &F { 45 | &self.a 46 | } 47 | 48 | /// Return a mutable reference to `a`. 49 | pub fn a_mut(&mut self) -> &mut F { 50 | &mut self.a 51 | } 52 | 53 | /// Return a reference to `self` as a tuple `(mu, sigma, a)`. 54 | pub fn as_tuple(&self) -> (&F, &F, &F) { 55 | (&self.mu, &self.sigma, &self.a) 56 | } 57 | 58 | /// Return a mutable reference to `self` as a tuple `(mu, sigma, a)`. 59 | pub fn as_mut_tuple(&mut self) -> (&mut F, &mut F, &mut F) { 60 | (&mut self.mu, &mut self.sigma, &mut self.a) 61 | } 62 | 63 | /// Returns a copy of `self` as a new tuple `(mu, sigma, a)`. 64 | pub fn to_tuple(&self) -> (F, F, F) { 65 | (self.mu, self.sigma, self.a) 66 | } 67 | 68 | /// Consume `self` and returns a tuple `(mu, sigma, a)`. 69 | pub fn into_tuple(self) -> (F, F, F) { 70 | (self.mu, self.sigma, self.a) 71 | } 72 | 73 | /// Returns a value of gaussian function. 74 | /// 75 | /// # Examples 76 | /// Returns a value of gaussian function. 77 | /// 78 | /// ``` 79 | /// use fitting::Gaussian; 80 | /// 81 | /// let gaussian = Gaussian::new(5., 3., 1.); 82 | /// let x = 5.; 83 | /// let y = gaussian.value(x); 84 | /// assert_eq!(&y, gaussian.a()); 85 | /// 86 | /// ``` 87 | pub fn value(&self, x: F) -> F { 88 | operations::value(x, self.mu, self.sigma, self.a) 89 | } 90 | 91 | /// Returns the values of gaussian function. 92 | /// 93 | /// # Examples 94 | /// Returns the values of gaussian function. 95 | /// 96 | /// ``` 97 | /// use fitting::approx::assert_abs_diff_eq; 98 | /// use fitting::Gaussian; 99 | /// use fitting::ndarray::{array, Array, Array1}; 100 | /// 101 | /// let gaussian = Gaussian::new(5., 3., 1.); 102 | /// let x_vec: Array1 = Array::range(1., 10., 1.); 103 | /// let y_vec: Array1 = gaussian.values(x_vec); 104 | /// let expected_ans = array![ 105 | /// 0.41111229050718745, 106 | /// 0.6065306597126334, 107 | /// 0.8007374029168081, 108 | /// 0.9459594689067654, 109 | /// 1., 110 | /// 0.9459594689067654, 111 | /// 0.8007374029168081, 112 | /// 0.6065306597126334, 113 | /// 0.41111229050718745 114 | /// ]; 115 | /// assert_abs_diff_eq!(&y_vec, &expected_ans, epsilon = 1e-9); 116 | /// ``` 117 | pub fn values(&self, x_vec: Array1) -> Array1 { 118 | operations::values(x_vec, self.mu, self.sigma, self.a) 119 | } 120 | 121 | /// Estimates the parameters of gaussian function for generic data. 122 | /// The return value is `(mu, sigma, a)` 123 | /// 124 | /// This function implements the [Guos Algorithm](https://www.researchgate.net/publication/281907940_Comparison_of_Algorithms_For_Fitting_a_Gaussian_Function_Used_in_Testing_Smart_Sensors). 125 | /// 126 | /// # Examples 127 | /// Estimates the parameters for sample data. 128 | /// 129 | /// ``` 130 | /// use fitting::approx::assert_abs_diff_eq; 131 | /// use fitting::Gaussian; 132 | /// use fitting::ndarray::{array, Array, Array1}; 133 | /// 134 | /// let gaussian = Gaussian::new(5., 3., 1.); 135 | /// let x_vec: Array1 = Array::range(1., 10., 1.); 136 | /// let y_vec: Array1 = gaussian.values(x_vec.clone()); 137 | /// let estimated = Gaussian::fit(x_vec, y_vec).unwrap(); 138 | /// assert_abs_diff_eq!(gaussian, estimated, epsilon = 1e-9); 139 | /// ``` 140 | /// 141 | /// # Limitations 142 | /// 143 | /// - `x_vec` must have at least one element 144 | /// - All elements of `y_vec` must be positive numbers 145 | /// - If the distribution is "long-tailed", it affects the fitting accuracy. 146 | /// - A detailed description can be found in Reference 1. 147 | /// - Also, you can see the reproduction and workaround in [GitHub](https://github.com/tasshi-me/fitting-rs/issues/6#issuecomment-1367095969). 148 | /// 149 | /// # References 150 | /// \[1\] [E. Pastuchov ́a and M. Z ́akopˇcan, ”Comparison of Algorithms for Fitting a Gaussian Function used in Testing Smart Sensors”, Journal of Electrical Engineering, vol. 66, no. 3, pp. 178-181, 2015.](https://www.researchgate.net/publication/281907940_Comparison_of_Algorithms_For_Fitting_a_Gaussian_Function_Used_in_Testing_Smart_Sensors) 151 | pub fn fit(x_vec: Array1, y_vec: Array1) -> Result, GaussianError> { 152 | let (mu, sigma, a) = operations::fitting_guos(x_vec, y_vec)?; 153 | Ok(Gaussian::::new(mu, sigma, a)) 154 | } 155 | } 156 | 157 | impl From<(F, F, F)> for Gaussian { 158 | fn from(tuple: (F, F, F)) -> Self { 159 | Gaussian::new(tuple.0, tuple.1, tuple.2) 160 | } 161 | } 162 | 163 | impl From> for (F, F, F) { 164 | fn from(gaussian: Gaussian) -> Self { 165 | (gaussian.mu, gaussian.sigma, gaussian.a) 166 | } 167 | } 168 | 169 | impl TryFrom> for Gaussian { 170 | type Error = &'static str; 171 | 172 | fn try_from(arr: Array1) -> Result { 173 | let error = "The index out of bounds."; 174 | Ok(Gaussian::new( 175 | *arr.get(0).ok_or(error)?, 176 | *arr.get(1).ok_or(error)?, 177 | *arr.get(2).ok_or(error)?, 178 | )) 179 | } 180 | } 181 | 182 | impl From> for Array1 { 183 | fn from(gaussian: Gaussian) -> Self { 184 | array![gaussian.mu, gaussian.sigma, gaussian.a] 185 | } 186 | } 187 | 188 | impl AbsDiffEq for Gaussian 189 | where 190 | F::Epsilon: Copy, 191 | { 192 | type Epsilon = F::Epsilon; 193 | 194 | fn default_epsilon() -> F::Epsilon { 195 | F::default_epsilon() 196 | } 197 | 198 | fn abs_diff_eq(&self, other: &Self, epsilon: F::Epsilon) -> bool { 199 | F::abs_diff_eq(&self.mu, &other.mu, epsilon) 200 | && F::abs_diff_eq(&self.sigma, &other.sigma, epsilon) 201 | && F::abs_diff_eq(&self.a, &other.a, epsilon) 202 | } 203 | } 204 | 205 | impl RelativeEq for Gaussian 206 | where 207 | F::Epsilon: Copy, 208 | { 209 | fn default_max_relative() -> F::Epsilon { 210 | F::default_max_relative() 211 | } 212 | 213 | fn relative_eq(&self, other: &Self, epsilon: F::Epsilon, max_relative: F::Epsilon) -> bool { 214 | F::relative_eq(&self.mu, &other.mu, epsilon, max_relative) 215 | && F::relative_eq(&self.sigma, &other.sigma, epsilon, max_relative) 216 | && F::relative_eq(&self.a, &other.a, epsilon, max_relative) 217 | } 218 | } 219 | 220 | impl UlpsEq for Gaussian 221 | where 222 | F::Epsilon: Copy, 223 | { 224 | fn default_max_ulps() -> u32 { 225 | F::default_max_ulps() 226 | } 227 | 228 | fn ulps_eq(&self, other: &Self, epsilon: F::Epsilon, max_ulps: u32) -> bool { 229 | F::ulps_eq(&self.mu, &other.mu, epsilon, max_ulps) 230 | && F::ulps_eq(&self.sigma, &other.sigma, epsilon, max_ulps) 231 | && F::ulps_eq(&self.a, &other.a, epsilon, max_ulps) 232 | } 233 | } 234 | 235 | #[cfg(test)] 236 | mod tests { 237 | use super::*; 238 | use approx::assert_abs_diff_eq; 239 | use ndarray::Array; 240 | use std::convert::TryInto; 241 | 242 | #[test] 243 | fn constructor() { 244 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 245 | let gaussian = Gaussian::new(mu, sigma, a); 246 | assert_eq!(gaussian.mu, mu); 247 | assert_eq!(gaussian.sigma, sigma); 248 | assert_eq!(gaussian.a, a); 249 | } 250 | 251 | #[test] 252 | fn getter() { 253 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 254 | let gaussian = Gaussian { mu, sigma, a }; 255 | assert_eq!(gaussian.mu(), &mu); 256 | assert_eq!(gaussian.sigma(), &sigma); 257 | assert_eq!(gaussian.a(), &a); 258 | } 259 | 260 | #[test] 261 | fn setter() { 262 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 263 | let mut gaussian = Gaussian { mu, sigma, a }; 264 | let (mu, sigma, a): (f64, f64, f64) = (4., 5., 6.); 265 | *gaussian.mu_mut() = mu; 266 | *gaussian.sigma_mut() = sigma; 267 | *gaussian.a_mut() = a; 268 | assert_eq!(gaussian.mu, mu); 269 | assert_eq!(gaussian.sigma, sigma); 270 | assert_eq!(gaussian.a, a); 271 | } 272 | 273 | #[test] 274 | fn value() { 275 | let gaussian = Gaussian::new(5., 3., 1.); 276 | let x = 5.; 277 | let y = gaussian.value(x); 278 | assert_eq!(&y, gaussian.a()); 279 | } 280 | 281 | #[test] 282 | fn values() { 283 | let gaussian = Gaussian::new(5., 3., 1.); 284 | let x_vec: Array1 = Array::range(1., 10., 1.); 285 | let y_vec: Array1 = gaussian.values(x_vec); 286 | let expected_ans = array![ 287 | 0.41111229050718745, 288 | 0.6065306597126334, 289 | 0.8007374029168081, 290 | 0.9459594689067654, 291 | 1., 292 | 0.9459594689067654, 293 | 0.8007374029168081, 294 | 0.6065306597126334, 295 | 0.41111229050718745 296 | ]; 297 | assert_abs_diff_eq!(&y_vec, &expected_ans, epsilon = 1e-9); 298 | } 299 | 300 | #[test] 301 | fn fit() { 302 | let gaussian = Gaussian::new(5., 3., 1.); 303 | let x_vec: Array1 = Array::range(1., 10., 1.); 304 | let y_vec: Array1 = gaussian.values(x_vec.clone()); 305 | let estimated = Gaussian::fit(x_vec, y_vec).unwrap(); 306 | assert_abs_diff_eq!(gaussian, estimated, epsilon = 1e-9); 307 | } 308 | 309 | #[test] 310 | fn fit_with_negative_value() { 311 | let x_vec: Array1 = array![1., 2., 3.]; 312 | let y_vec: Array1 = array![-1., 1., -1.]; 313 | 314 | let err = Gaussian::fit(x_vec, y_vec).unwrap_err(); 315 | assert_eq!(err, GaussianError::GivenYVecContainsNegativeValue); 316 | } 317 | 318 | #[test] 319 | fn as_tuple() { 320 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 321 | let gaussian = Gaussian { mu, sigma, a }; 322 | assert_eq!(gaussian.as_tuple(), (&mu, &sigma, &a)); 323 | } 324 | 325 | #[test] 326 | fn as_mut_tuple() { 327 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 328 | let mut gaussian = Gaussian { mu, sigma, a }; 329 | let (mu, sigma, a): (f64, f64, f64) = (4., 5., 6.); 330 | let (mu_mut, sigma_mut, a_mut) = gaussian.as_mut_tuple(); 331 | *mu_mut = mu; 332 | *sigma_mut = sigma; 333 | *a_mut = a; 334 | assert_eq!(gaussian.mu, mu); 335 | assert_eq!(gaussian.sigma, sigma); 336 | assert_eq!(gaussian.a, a); 337 | } 338 | 339 | #[test] 340 | fn to_tuple() { 341 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 342 | let gaussian = Gaussian { mu, sigma, a }; 343 | assert_eq!(gaussian.to_tuple(), (mu, sigma, a)); 344 | } 345 | 346 | #[test] 347 | fn into_tuple() { 348 | let (mu, sigma, a): (f64, f64, f64) = (1., 2., 3.); 349 | let gaussian = Gaussian { mu, sigma, a }; 350 | assert_eq!(gaussian.into_tuple(), (mu, sigma, a)); 351 | } 352 | 353 | #[test] 354 | fn from_into_tuple() { 355 | let input: (f64, f64, f64) = (1., 2., 3.); 356 | // From tuple 357 | let gaussian = Gaussian::from(input); 358 | assert_eq!(input, (gaussian.mu, gaussian.sigma, gaussian.a)); 359 | 360 | // Into tuple 361 | let output: (_, _, _) = gaussian.into(); 362 | assert_eq!(input, output); 363 | 364 | // From Gaussian 365 | // Skip 366 | 367 | // Into Gaussian 368 | let gaussian: Gaussian<_> = input.into(); 369 | assert_eq!(input, (gaussian.mu, gaussian.sigma, gaussian.a)); 370 | } 371 | 372 | #[test] 373 | fn from_into_ndarray1() { 374 | let input = array![1., 2., 3.]; 375 | // TryFrom Array1 success 376 | let gaussian = Gaussian::try_from(input.clone()).unwrap(); 377 | assert_eq!(input, array![gaussian.mu, gaussian.sigma, gaussian.a]); 378 | 379 | // TryFrom Array1 failure 380 | let input_failure = array![1.,]; 381 | Gaussian::try_from(input_failure).unwrap_err(); 382 | 383 | // Into Array1 384 | let output: Array1<_> = gaussian.into(); 385 | assert_eq!(input, output); 386 | 387 | // From Gaussian 388 | let output = Array1::from(gaussian); 389 | assert_eq!(input, output); 390 | 391 | // TryInto Gaussian success 392 | let gaussian: Gaussian<_> = input.clone().try_into().unwrap(); 393 | assert_eq!(input, array![gaussian.mu, gaussian.sigma, gaussian.a]); 394 | 395 | // TryInto Gaussian failure 396 | let input_failure = array![1.,]; 397 | let result: Result, _> = input_failure.try_into(); 398 | result.unwrap_err(); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/gaussian/operations.rs: -------------------------------------------------------------------------------- 1 | use crate::gaussian::GaussianError; 2 | use crate::linalg; 3 | use crate::linalg::Float; 4 | use ndarray::{array, Array1}; 5 | 6 | pub fn value(x: F, mu: F, sigma: F, a: F) -> F { 7 | a * (-(x - mu).powi(2) / (F::from(2).unwrap() * sigma.powi(2))).exp() 8 | } 9 | 10 | pub fn values(x_vec: Array1, mu: F, sigma: F, a: F) -> Array1 { 11 | x_vec.iter().map(|x| value(*x, mu, sigma, a)).collect() 12 | } 13 | 14 | #[allow(dead_code)] 15 | pub fn fitting_caruanas( 16 | x_vec: Array1, 17 | y_vec: Array1, 18 | ) -> Result<(F, F, F), GaussianError> { 19 | let len_x_vec = F::from(x_vec.len()).ok_or(GaussianError::GivenXVecHasNoElement)?; 20 | let sum_x = x_vec.sum(); 21 | let sum_x_pow2 = x_vec.iter().map(|x| x.powi(2)).sum(); 22 | let sum_x_pow3 = x_vec.iter().map(|x| x.powi(3)).sum(); 23 | let sum_x_pow4 = x_vec.iter().map(|x| x.powi(4)).sum(); 24 | let a = array![ 25 | [len_x_vec, sum_x, sum_x_pow2], 26 | [sum_x, sum_x_pow2, sum_x_pow3], 27 | [sum_x_pow2, sum_x_pow3, sum_x_pow4], 28 | ]; 29 | 30 | let sum_log_y = y_vec.iter().map(|y| y.ln()).sum(); 31 | let sum_x_log_y = y_vec 32 | .iter() 33 | .zip(x_vec.iter()) 34 | .map(|(y, x)| y.ln() * *x) 35 | .sum(); 36 | let sum_x_pow2_log_y = y_vec 37 | .iter() 38 | .zip(x_vec.iter()) 39 | .map(|(y, x)| y.ln() * x.powi(2)) 40 | .sum(); 41 | let b = array![sum_log_y, sum_x_log_y, sum_x_pow2_log_y]; 42 | 43 | let ans_x = linalg::solve(a, b)?; 44 | let (a, b, c) = (ans_x[0], ans_x[1], ans_x[2]); 45 | 46 | let mu = -b / (F::from(2).unwrap() * c); 47 | let sigma = (-F::one() / (F::from(2).unwrap() * c)).sqrt(); 48 | let a = (a - (b.powi(2) / (F::from(4).unwrap() * c))).exp(); 49 | 50 | Ok((mu, sigma, a)) 51 | } 52 | 53 | pub fn fitting_guos( 54 | x_vec: Array1, 55 | y_vec: Array1, 56 | ) -> Result<(F, F, F), GaussianError> { 57 | // Guo's algorithm doesn't support negative value in y[] 58 | if y_vec.iter().any(|y| y.is_sign_negative()) { 59 | return Err(GaussianError::GivenYVecContainsNegativeValue); 60 | } 61 | let sum_y_pow2: F = y_vec.iter().map(|y| y.powi(2)).sum(); 62 | let sum_x_y_pow2 = y_vec 63 | .iter() 64 | .zip(x_vec.iter()) 65 | .map(|(y, x)| *x * y.powi(2)) 66 | .sum(); 67 | let sum_x_pow2_y_pow2 = y_vec 68 | .iter() 69 | .zip(x_vec.iter()) 70 | .map(|(y, x)| x.powi(2) * y.powi(2)) 71 | .sum(); 72 | let sum_x_pow3_y_pow2 = y_vec 73 | .iter() 74 | .zip(x_vec.iter()) 75 | .map(|(y, x)| x.powi(3) * y.powi(2)) 76 | .sum(); 77 | let sum_x_pow4_y_pow2 = y_vec 78 | .iter() 79 | .zip(x_vec.iter()) 80 | .map(|(y, x)| x.powi(4) * y.powi(2)) 81 | .sum(); 82 | 83 | let a = array![ 84 | [sum_y_pow2, sum_x_y_pow2, sum_x_pow2_y_pow2], 85 | [sum_x_y_pow2, sum_x_pow2_y_pow2, sum_x_pow3_y_pow2], 86 | [sum_x_pow2_y_pow2, sum_x_pow3_y_pow2, sum_x_pow4_y_pow2], 87 | ]; 88 | 89 | let sum_y_pow2_log_y: F = y_vec.iter().map(|y| y.powi(2) * y.ln()).sum(); 90 | let sum_x_y_pow2_log_y = y_vec 91 | .iter() 92 | .zip(x_vec.iter()) 93 | .map(|(y, x)| *x * y.powi(2) * y.ln()) 94 | .sum(); 95 | let sum_x_pow2_y_pow2_log_y = y_vec 96 | .iter() 97 | .zip(x_vec.iter()) 98 | .map(|(y, x)| x.powi(2) * y.powi(2) * y.ln()) 99 | .sum(); 100 | let b = array![ 101 | sum_y_pow2_log_y, 102 | sum_x_y_pow2_log_y, 103 | sum_x_pow2_y_pow2_log_y, 104 | ]; 105 | 106 | let ans_x = linalg::solve(a, b)?; 107 | let (a, b, c) = (ans_x[0], ans_x[1], ans_x[2]); 108 | 109 | let mu = -b / (F::from(2).unwrap() * c); 110 | let sigma = ((-F::one() / (F::from(2).unwrap() * c)) as F).sqrt(); 111 | let a = ((a - (b.powi(2) / (F::from(4).unwrap() * c))) as F).exp(); 112 | 113 | Ok((mu, sigma, a)) 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::*; 119 | use approx::assert_abs_diff_eq; 120 | use ndarray::{array, Array}; 121 | 122 | #[test] 123 | fn gaussian_value() { 124 | let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 125 | let x = 5.; 126 | let y = value(x, mu, sigma, a); 127 | assert_eq!(y, a); 128 | } 129 | 130 | #[test] 131 | fn gaussian_values() { 132 | let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 133 | let x_vec: Array1 = Array::range(1., 10., 1.); 134 | let y_vec: Array1 = values(x_vec, mu, sigma, a); 135 | let expected_ans = array![ 136 | 0.41111229050718745, 137 | 0.6065306597126334, 138 | 0.8007374029168081, 139 | 0.9459594689067654, 140 | 1., 141 | 0.9459594689067654, 142 | 0.8007374029168081, 143 | 0.6065306597126334, 144 | 0.41111229050718745 145 | ]; 146 | assert_abs_diff_eq!(&y_vec, &expected_ans, epsilon = 1e-9); 147 | } 148 | 149 | #[test] 150 | fn gaussian_fit_caruanas() { 151 | let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 152 | let x_vec: Array1 = Array::range(1., 10., 1.); 153 | let y_vec: Array1 = values(x_vec.clone(), mu, sigma, a); 154 | let estimated = fitting_caruanas(x_vec, y_vec).unwrap(); 155 | assert_abs_diff_eq!( 156 | &array![estimated.0, estimated.1, estimated.2], 157 | &array![mu, sigma, a], 158 | epsilon = 1e-9 159 | ); 160 | } 161 | 162 | #[test] 163 | fn gaussian_fit_guos() { 164 | let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 165 | let x_vec: Array1 = Array::range(1., 10., 1.); 166 | let y_vec: Array1 = values(x_vec.clone(), mu, sigma, a); 167 | let estimated = fitting_guos(x_vec, y_vec).unwrap(); 168 | assert_abs_diff_eq!( 169 | &array![estimated.0, estimated.1, estimated.2], 170 | &array![mu, sigma, a], 171 | epsilon = 1e-9 172 | ); 173 | } 174 | 175 | #[test] 176 | fn gaussian_fit_guos_y_vec_contains_negative_value() { 177 | let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.); 178 | let x_vec: Array1 = Array::range(1., 10., 1.); 179 | let y_vec: Array1 = values(x_vec.clone(), mu, sigma, a).map(|y| y - 0.5); 180 | 181 | let err = fitting_guos(x_vec, y_vec).unwrap_err(); 182 | assert_eq!(err, GaussianError::GivenYVecContainsNegativeValue); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //!
2 | //! 3 | //! This library provides [`fitting::Gaussian`][crate::Gaussian], which represents gaussian function 4 | //! 5 | //!
6 | //! 7 | //! # Details 8 | //! 9 | //! Use [`Gaussian::new`] to get struct that represents gaussian function. 10 | //! 11 | //! ``` 12 | //! use fitting::Gaussian; 13 | //! 14 | //! let gaussian = Gaussian::new(5., 3., 1.); 15 | //! let x = 5.; 16 | //! let y = gaussian.value(x); 17 | //! assert_eq!(&y, gaussian.a()); 18 | //! 19 | //! ``` 20 | //! 21 | //! Use [`Gaussian::value`] and [`Gaussian::values`] to get value(s) of the function. 22 | //! 23 | //! ``` 24 | //! use fitting::Gaussian; 25 | //! 26 | //! let gaussian = Gaussian::new(5., 3., 1.); 27 | //! let x = 5.; 28 | //! let y = gaussian.value(x); 29 | //! assert_eq!(&y, gaussian.a()); 30 | //! 31 | //! ``` 32 | //! 33 | //! Use [`Gaussian::fit`] to fitting arrays to the gaussian function. 34 | //! 35 | //! ``` 36 | //! use fitting::approx::assert_abs_diff_eq; 37 | //! use fitting::Gaussian; 38 | //! use fitting::ndarray::{array, Array, Array1}; 39 | //! 40 | //! let gaussian = Gaussian::new(5., 3., 1.); 41 | //! let x_vec: Array1 = Array::range(1., 10., 1.); 42 | //! let y_vec: Array1 = gaussian.values(x_vec.clone()); 43 | //! let estimated = Gaussian::fit(x_vec, y_vec).unwrap(); 44 | //! assert_abs_diff_eq!(gaussian, estimated, epsilon = 1e-9); 45 | //! ``` 46 | //! 47 | 48 | pub mod gaussian; 49 | pub mod linalg; 50 | 51 | #[doc(inline)] 52 | pub use self::gaussian::Gaussian; 53 | 54 | // external dependencies 55 | pub use approx; 56 | pub use ndarray; 57 | pub use serde; 58 | -------------------------------------------------------------------------------- /src/linalg.rs: -------------------------------------------------------------------------------- 1 | use approx::{abs_diff_eq, abs_diff_ne}; 2 | use ndarray::{s, Array1, Array2, Axis}; 3 | 4 | use approx::{AbsDiffEq, RelativeEq, UlpsEq}; 5 | use ndarray::NdFloat; 6 | use std::iter::Sum; 7 | 8 | pub trait Float: NdFloat + Sum + AbsDiffEq + RelativeEq + UlpsEq {} 9 | impl Float for F {} 10 | 11 | use thiserror::Error; 12 | 13 | #[derive(Error, Debug, Eq, PartialEq)] 14 | pub enum LinalgError { 15 | /// Equations have no solutions 16 | #[error("Equations have no solutions")] 17 | EquationsHaveNoSolutions, 18 | /// Equations have infinite solutions 19 | #[error("Equations have infinite solutions")] 20 | EquationsHaveInfSolutions, 21 | } 22 | 23 | /// Solves a system of linear equations. 24 | /// 25 | /// This function implements the Gaussian elimination. 26 | /// # Examples 27 | /// Solves `a * x = b`. 28 | /// 29 | /// ``` 30 | /// use fitting::approx::assert_abs_diff_eq; 31 | /// use fitting::linalg; 32 | /// use fitting::ndarray::array; 33 | /// 34 | /// let a = array![[3., 2., -1.], [2., -2., 4.], [-2., 1., -2.]]; 35 | /// let b = array![1., -2., 0.]; 36 | /// let x = linalg::solve(a, b).unwrap(); 37 | /// assert_abs_diff_eq!(x, array![1., -2., -2.], epsilon = 1e-9); 38 | /// ``` 39 | pub fn solve(a: Array2, b: Array1) -> Result, LinalgError> { 40 | let mut a = a; 41 | let mut b = b; 42 | 43 | // forward elimination 44 | for i in 0..(a.nrows() - 1) { 45 | // partial pivoting 46 | let (pivot_index, _) = a.column(i).iter().enumerate().skip(i).fold( 47 | (i, a[[i, i]]), 48 | |(max_index, max), (index, val)| { 49 | if val.abs() > max { 50 | (index, *val) 51 | } else { 52 | (max_index, max) 53 | } 54 | }, 55 | ); 56 | if i != pivot_index { 57 | let (mut a1, mut a2) = a.view_mut().split_at(Axis(0), pivot_index); 58 | ndarray::Zip::from(a1.row_mut(i)) 59 | .and(a2.row_mut(0)) 60 | .for_each(::std::mem::swap); 61 | b.swap(i, pivot_index); 62 | } 63 | for j in i + 1..a.nrows() { 64 | let coefficient = a[[j, i]] / a[[i, i]]; 65 | // a[j] -= a[i] * coefficient; 66 | let a_i = a.row(i).to_owned(); 67 | let b_i = b[i]; 68 | let mut view = a.row_mut(j); 69 | view -= &(&a_i * coefficient); 70 | b[j] -= b_i * coefficient; 71 | } 72 | } 73 | 74 | // check rank of matrix 75 | // rank_coef: rank of coefficient matrix (given a) 76 | // rank_aug: rank of augmented matrix 77 | let mut rank_coef = a.nrows(); 78 | for index in (0..a.nrows()).rev() { 79 | if a.row(index) 80 | .iter() 81 | .all(|val| abs_diff_eq!(*val, F::zero()) || val.is_nan()) 82 | { 83 | rank_coef -= 1; 84 | } else { 85 | break; 86 | } 87 | } 88 | let rank_coef = rank_coef; 89 | 90 | let mut rank_aug = rank_coef; 91 | for index in ((rank_coef - 1)..a.nrows()).rev() { 92 | if abs_diff_ne!(b[index], F::zero()) && !b[index].is_nan() { 93 | rank_aug = index + 1; 94 | break; 95 | } 96 | } 97 | let rank_aug = rank_aug; 98 | 99 | // no solutions 100 | if rank_coef != rank_aug { 101 | return Err(LinalgError::EquationsHaveNoSolutions); 102 | } 103 | 104 | // infinite solutions 105 | if rank_coef != a.ncols() { 106 | return Err(LinalgError::EquationsHaveInfSolutions); 107 | } 108 | 109 | // backward substitution 110 | for i in (0..rank_coef).rev() { 111 | b[i] /= a[[i, i]]; 112 | // a[i] /= a[i][i]; 113 | let a_i_i = a[[i, i]]; 114 | let mut view = a.row_mut(i); 115 | view /= a_i_i; 116 | for j in 0..i { 117 | let b_i = b[i]; 118 | b[j] -= b_i * a[[j, i]]; 119 | a[[j, i]] = F::zero(); 120 | } 121 | } 122 | Ok(b.slice(s![0..rank_coef]).to_owned()) 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | use approx::assert_abs_diff_eq; 129 | use ndarray::array; 130 | 131 | #[test] 132 | fn linalg_solve_2x2() { 133 | let a = array![[3., 1.], [1., 2.]]; 134 | let b = array![9., 8.]; 135 | let x = solve(a, b).unwrap(); 136 | assert_eq!(x, array![2., 3.]); 137 | } 138 | 139 | #[test] 140 | fn linalg_solve_3x3() { 141 | let a = array![[3., 2., -1.], [2., -2., 4.], [-2., 1., -2.]]; 142 | let b = array![1., -2., 0.]; 143 | let x = solve(a, b).unwrap(); 144 | assert_abs_diff_eq!(x, array![1., -2., -2.], epsilon = 1e-9); 145 | } 146 | 147 | #[test] 148 | fn linalg_solve_pivoting() { 149 | let a = array![[2., 4., -2.], [1., 2., 1.], [1., 3., 2.],]; 150 | let b = array![8., 6., 9.]; 151 | let x = solve(a, b).unwrap(); 152 | println!("{:?}", x); 153 | assert_abs_diff_eq!(x, array![1., 2., 1.], epsilon = 1e-9); 154 | } 155 | 156 | #[test] 157 | // one solution (4x4) 158 | // rank_coef: 4 159 | // rank_aug: 4 160 | // coef ncols: 4 161 | fn linalg_solve_has_one_solution() { 162 | let a = array![ 163 | [2., 1., -3., -2.], 164 | [2., -1., -1., 3.], 165 | [1., -1., -2., 2.], 166 | [-1., 1., 3., -2.] 167 | ]; 168 | let b = array![-4., 1., -3., 5.]; 169 | let x = solve(a, b).unwrap(); 170 | assert_abs_diff_eq!(x, array![1., 2., 2., 1.], epsilon = 1e-9); 171 | } 172 | 173 | #[test] 174 | // one solution (4x3) 175 | // rank_coef: 3 176 | // rank_aug: 3 177 | // coef ncols: 3 178 | fn linalg_solve_has_one_solution_2() { 179 | let a = array![[2., 1., -3.], [2., -1., -1.], [1., -1., -2.], [-1., 1., 3.]]; 180 | let b = array![-2., -2., -5., 7.]; 181 | let x = solve(a, b).unwrap(); 182 | assert_abs_diff_eq!(x, array![1., 2., 2.], epsilon = 1e-9); 183 | } 184 | 185 | #[test] 186 | // inf solutions (3x4) 187 | // rank_coef: 3 188 | // coef ncols: 4 189 | fn linalg_solve_has_inf_solutions() { 190 | let a = array![[2., 1., -3., -2.], [2., -1., -1., 3.], [1., -1., -2., 2.]]; 191 | let b = array![4., 1., -3.]; 192 | let err = solve(a, b).unwrap_err(); //panic 193 | assert_eq!(err, LinalgError::EquationsHaveInfSolutions); 194 | } 195 | 196 | #[test] 197 | // inf solutions (4x4) 198 | // rank_coef: 2 199 | // coef ncols: 4 200 | fn linalg_solve_has_inf_solutions_2() { 201 | let a = array![ 202 | [2., 1., 3., 4.], 203 | [2., -3., -1., -4.], 204 | [1., -2., -1., -3.], 205 | [-1., 2., 1., 3.] 206 | ]; 207 | let b = array![2., -6. / 5., -1., 1.]; 208 | let err = solve(a, b).unwrap_err(); //panic 209 | assert_eq!(err, LinalgError::EquationsHaveInfSolutions); 210 | } 211 | 212 | #[test] 213 | // no solutions (3x2) 214 | // rank_coef: 2 215 | // rank_aug: 3 216 | fn linalg_solve_has_no_solutions() { 217 | let a = array![[-2., 3.], [4., 1.], [1., -3.],]; 218 | let b = array![1., 5., -1.]; 219 | let err = solve(a, b).unwrap_err(); //panic 220 | assert_eq!(err, LinalgError::EquationsHaveNoSolutions); 221 | } 222 | 223 | #[test] 224 | // no solutions (3x3) 225 | // rank_coef: 2 226 | // rank_aug: 3 227 | fn linalg_solve_has_no_solutions_2() { 228 | let a = array![[1., 3., -2.], [-1., 2., -3.], [2., -1., 3.],]; 229 | let b = array![2., -2., 3.]; 230 | let err = solve(a, b).unwrap_err(); //panic 231 | assert_eq!(err, LinalgError::EquationsHaveNoSolutions); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | 5 | #[test] 6 | fn it_works() {} 7 | 8 | #[test] 9 | #[should_panic] 10 | fn it_does_not_works() { 11 | assert!(false); 12 | } 13 | --------------------------------------------------------------------------------