| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- // tools/convolution_calculator.dart
- import 'package:flutter/material.dart';
- import 'package:rational/rational.dart';
- class ConvolutionCalculatorPage extends StatefulWidget {
- @override
- _ConvolutionCalculatorPageState createState() => _ConvolutionCalculatorPageState();
- }
- class _ConvolutionCalculatorPageState extends State<ConvolutionCalculatorPage> {
- final List<TextEditingController> _xValueControllers = [];
- final List<TextEditingController> _xProbControllers = [];
- final List<TextEditingController> _yValueControllers = [];
- final List<TextEditingController> _yProbControllers = [];
-
- List<Map<String, Rational>> _convolutionResult = [];
- bool _showResult = false;
- @override
- void initState() {
- super.initState();
- // 初始化默认数据
- _initializeDefaultData();
- }
- void _initializeDefaultData() {
- // 添加X的默认数据
- _addXRow(value: '0', prob: '1/2');
- _addXRow(value: '1', prob: '1/2');
-
- // 添加Y的默认数据
- _addYRow(value: '0', prob: '1/3');
- _addYRow(value: '1', prob: '1/3');
- _addYRow(value: '2', prob: '1/3');
- }
- void _addXRow({String value = '', String prob = ''}) {
- setState(() {
- _xValueControllers.add(TextEditingController(text: value));
- _xProbControllers.add(TextEditingController(text: prob));
- });
- }
- void _removeXRow(int index) {
- setState(() {
- _xValueControllers.removeAt(index);
- _xProbControllers.removeAt(index);
- });
- }
- void _addYRow({String value = '', String prob = ''}) {
- setState(() {
- _yValueControllers.add(TextEditingController(text: value));
- _yProbControllers.add(TextEditingController(text: prob));
- });
- }
- void _removeYRow(int index) {
- setState(() {
- _yValueControllers.removeAt(index);
- _yProbControllers.removeAt(index);
- });
- }
- Rational _parseRational(String input) {
- try {
- if (input.contains('/')) {
- List<String> parts = input.split('/');
- if (parts.length == 2) {
- return Rational.parse(parts[0].trim()) / Rational.parse(parts[1].trim());
- }
- }
- return Rational.parse(input.trim());
- } catch (e) {
- throw FormatException('无效的概率格式: $input');
- }
- }
- void _calculateConvolution() {
- try {
- // 解析X分布
- Map<Rational, Rational> xDistribution = {};
- Rational xSum = Rational.zero;
- for (int i = 0; i < _xValueControllers.length; i++) {
- Rational value = _parseRational(_xValueControllers[i].text);
- Rational prob = _parseRational(_xProbControllers[i].text);
- xDistribution[value] = prob;
- xSum += prob;
- }
- // 解析Y分布
- Map<Rational, Rational> yDistribution = {};
- Rational ySum = Rational.zero;
- for (int i = 0; i < _yValueControllers.length; i++) {
- Rational value = _parseRational(_yValueControllers[i].text);
- Rational prob = _parseRational(_yProbControllers[i].text);
- yDistribution[value] = prob;
- ySum += prob;
- }
- // 验证概率和是否为1
- if (xSum != Rational.one) {
- throw FormatException('X的概率和必须为1,当前为: $xSum');
- }
- if (ySum != Rational.one) {
- throw FormatException('Y的概率和必须为1,当前为: $ySum');
- }
- // 计算卷积
- Map<Rational, Rational> convolution = {};
- for (var xEntry in xDistribution.entries) {
- for (var yEntry in yDistribution.entries) {
- Rational sumValue = xEntry.key + yEntry.key;
- Rational prob = xEntry.value * yEntry.value;
- convolution[sumValue] = (convolution[sumValue] ?? Rational.zero) + prob;
- }
- }
- // 转换为列表并排序
- _convolutionResult = convolution.entries
- .map((entry) => {'value': entry.key, 'probability': entry.value})
- .toList();
-
- _convolutionResult.sort((a, b) => a['value']!.compareTo(b['value']!));
- setState(() {
- _showResult = true;
- });
- } catch (e) {
- _showErrorDialog(e.toString());
- }
- }
- void _showErrorDialog(String message) {
- showDialog(
- context: context,
- builder: (context) => AlertDialog(
- title: Text('计算错误'),
- content: Text(message),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(),
- child: Text('确定'),
- ),
- ],
- ),
- );
- }
- void _clearAll() {
- setState(() {
- _xValueControllers.clear();
- _xProbControllers.clear();
- _yValueControllers.clear();
- _yProbControllers.clear();
- _convolutionResult.clear();
- _showResult = false;
- _initializeDefaultData();
- });
- }
- @override
- void dispose() {
- for (var controller in _xValueControllers) {
- controller.dispose();
- }
- for (var controller in _xProbControllers) {
- controller.dispose();
- }
- for (var controller in _yValueControllers) {
- controller.dispose();
- }
- for (var controller in _yProbControllers) {
- controller.dispose();
- }
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text('卷积分布列计算'),
- backgroundColor: Colors.white,
- elevation: 1,
- actions: [
- IconButton(
- icon: Icon(Icons.clear_all),
- onPressed: _clearAll,
- tooltip: '清空所有',
- ),
- ],
- ),
- body: SingleChildScrollView(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _buildDistributionInput('随机变量 X 的分布列', _xValueControllers, _xProbControllers, _addXRow, _removeXRow),
- SizedBox(height: 24),
- _buildDistributionInput('随机变量 Y 的分布列', _yValueControllers, _yProbControllers, _addYRow, _removeYRow),
- SizedBox(height: 24),
- Center(
- child: ElevatedButton.icon(
- onPressed: _calculateConvolution,
- icon: Icon(Icons.calculate),
- label: Text('计算卷积分布列'),
- style: ElevatedButton.styleFrom(
- padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
- ),
- ),
- ),
- SizedBox(height: 24),
- if (_showResult) _buildResultTable(),
- ],
- ),
- ),
- );
- }
- Widget _buildDistributionInput(
- String title,
- List<TextEditingController> valueControllers,
- List<TextEditingController> probControllers,
- VoidCallback onAdd,
- Function(int) onRemove,
- ) {
- return Card(
- elevation: 2,
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 16),
- _buildTableHeader(),
- SizedBox(height: 8),
- ...List.generate(valueControllers.length, (index) {
- return _buildInputRow(
- valueControllers[index],
- probControllers[index],
- index,
- onRemove,
- valueControllers.length,
- );
- }),
- SizedBox(height: 16),
- Center(
- child: OutlinedButton.icon(
- onPressed: onAdd,
- icon: Icon(Icons.add, size: 18),
- label: Text('添加一行'),
- ),
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildTableHeader() {
- return Row(
- children: [
- Expanded(
- flex: 2,
- child: Text('取值', style: TextStyle(fontWeight: FontWeight.bold)),
- ),
- SizedBox(width: 16),
- Expanded(
- flex: 3,
- child: Text('概率 (支持分数如 1/2)', style: TextStyle(fontWeight: FontWeight.bold)),
- ),
- SizedBox(width: 16),
- Expanded(
- flex: 1,
- child: Text('操作', style: TextStyle(fontWeight: FontWeight.bold)),
- ),
- ],
- );
- }
- Widget _buildInputRow(
- TextEditingController valueController,
- TextEditingController probController,
- int index,
- Function(int) onRemove,
- int totalRows,
- ) {
- return Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Row(
- children: [
- Expanded(
- flex: 2,
- child: TextField(
- controller: valueController,
- decoration: InputDecoration(
- hintText: '如: 0, 1, 2...',
- border: OutlineInputBorder(),
- contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- ),
- keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
- ),
- ),
- SizedBox(width: 16),
- Expanded(
- flex: 3,
- child: TextField(
- controller: probController,
- decoration: InputDecoration(
- hintText: '如: 1/2, 0.25, 0.3',
- border: OutlineInputBorder(),
- contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- ),
- ),
- ),
- SizedBox(width: 16),
- Expanded(
- flex: 1,
- child: IconButton(
- icon: Icon(Icons.remove_circle, color: Colors.red),
- onPressed: totalRows > 1 ? () => onRemove(index) : null,
- tooltip: '删除此行',
- ),
- ),
- ],
- ),
- );
- }
- Widget _buildResultTable() {
- return Card(
- elevation: 2,
- color: Colors.green[50],
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '卷积分布列 X + Y',
- style: TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.bold,
- color: Colors.green[800],
- ),
- ),
- SizedBox(height: 16),
- _buildResultTableHeader(),
- ..._convolutionResult.map(_buildResultRow).toList(),
- SizedBox(height: 16),
- _buildProbabilitySum(),
- ],
- ),
- ),
- );
- }
- Widget _buildResultTableHeader() {
- return Container(
- padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
- decoration: BoxDecoration(
- color: Colors.green[100],
- borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
- ),
- child: Row(
- children: [
- Expanded(
- child: Text('X + Y 取值', style: TextStyle(fontWeight: FontWeight.bold)),
- ),
- Expanded(
- child: Text('概率 (分数)', style: TextStyle(fontWeight: FontWeight.bold)),
- ),
- Expanded(
- child: Text('概率 (小数)', style: TextStyle(fontWeight: FontWeight.bold)),
- ),
- ],
- ),
- );
- }
- Widget _buildResultRow(Map<String, Rational> entry) {
- Rational value = entry['value']!;
- Rational probability = entry['probability']!;
- double decimalProb = probability.toDouble();
- return Container(
- padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
- decoration: BoxDecoration(
- border: Border(bottom: BorderSide(color: Colors.green[100]!)),
- ),
- child: Row(
- children: [
- Expanded(
- child: Text(
- value.toString(),
- style: TextStyle(fontSize: 16),
- ),
- ),
- Expanded(
- child: Text(
- _toFractionString(probability),
- style: TextStyle(fontSize: 16, fontFamily: 'Monospace'),
- ),
- ),
- Expanded(
- child: Text(
- decimalProb.toStringAsFixed(6),
- style: TextStyle(fontSize: 16, fontFamily: 'Monospace'),
- ),
- ),
- ],
- ),
- );
- }
- String _toFractionString(Rational rational) {
- if (rational.denominator == BigInt.one) {
- return rational.numerator.toString();
- }
- return '${rational.numerator}/${rational.denominator}';
- }
- Widget _buildProbabilitySum() {
- Rational total = _convolutionResult.fold(
- Rational.zero,
- (sum, entry) => sum + entry['probability']!,
- );
- return Container(
- padding: EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: Colors.green[100],
- borderRadius: BorderRadius.circular(8),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text('概率总和:', style: TextStyle(fontWeight: FontWeight.bold)),
- Text(
- '${_toFractionString(total)} = ${total.toDouble().toStringAsFixed(6)}',
- style: TextStyle(fontWeight: FontWeight.bold, fontFamily: 'Monospace'),
- ),
- ],
- ),
- );
- }
- }
|